本篇是看完《深入理解C++11:C++11新特性解析与应用》后做的笔记的下半部分. 这本书可以看作是《C++Primer》的进阶版, 主要是更加详细地介绍了C++11的一些常用设计和标准库设施, 很多知识点都在面试中会遇到, 值得一读.
阅读并笔记的途中我跳过了一些之前已经总结过的内容, 而对于一些自己看书后依然没搞清楚的内容(例如SFINAE和内存模型)搜索资料进行了扩展, 还补充了一些原书没有介绍但稍微有所相关的内容, 参考文献在每一段的开头给出. 全文6.0k字, 慢慢来吧.
才疏学浅, 错漏在所难免, 后续若有所修改会同步存于我的Github仓库, 点击https://github.com/ZFhuang/Study-Notes/blob/main/Content/%E3%80%8A%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3C%2B%2B11%E3%80%8B%E7%AC%94%E8%AE%B0/%E4%B8%8B/README.md可跳转 Study-Notes/Content/《深入理解C++11》笔记/下 at main · ZFhuang/Study-Notes (github.com)
constexpr
来声明...
称为模板参数包, 模板参数包也可以是特化的...
来进行解包(包扩展)...
前面的直接成员进行多次使用sizeof...()
操作符来计算参数包中的参数数量, 返回一个size_t/// 经典的tuple定义, 利用上了各种变长模板特性
// 变长模板的声明
template<typename... Elements> class tuple;
// 利用偏特化作为模板的入口, 用继承结构来递归解包来逐渐得到各个参数
template<typename Head, typename... Tail>
class tuple<Head, Tail...> : private tuple<typename... Tail>{
Head head;
};
// 递归结束的条件
template<> class tuple{};
/// 新的基于函数模板的printf()
// 模板参数划分为当前参数和参数列表
// 当前参数列表的第一个参数作为类型T被捕获, 后面的留作递归
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args){
// 逐字符打印字符串
while(*s){
// 发现%时跳过这两个字符, 然后用cout打印
if(*s=='%' && *++s!='%'){
cout<<value;
// 移动到下一个字符, 然后将剩余的参数继续递归
return printf(++s, args);
}
// 否则直接打印当前字符串的字符即可
cout<<*s++;
}
// Error
}
// 递归结束的条件, 参数全部用完的情况, 此时应该直接能打印完剩下的全部字符串
void printf(const char* s){
while(*s){
if(*s=='%' && *++s!='%'){
// Error
}
cout<<*s++;
}
}
int main(){
// 调用测试
printf("print %s%d\n", string("a"), 1);
return 0;
}
/// 多种的参数包展开效果
// 变长右值引用
// 牢记...的本质是...在参数列表中进行了展开, 前面的类型进行了多次的扩展出现
template<typename... Args>
void foo(Args&&... args);
// 变长继承
template<typename T, typename... Args>
class Foo : private Foo<Arg>...{}
// 变长返回值作为参数
template<typename... Args>
void foo(Args... args){
// 此时args展开并调用到poo上然后又组成一个变长参数给boo使用
boo(poo(args)...);
}
// 模板作为参数包
template<typename T, template<typename> class A, template<typename> class... B>
class Foo<T, A, B...>{}
// 两个模板参数包(容易编译错误, 必须保证编译器能够推导)
template<
template<typename> class... A, typename... AArgs,
template<typename> class... B, typename... BArgs>
class Foo<A<AArgs...>, B<BArgs...>>{}
// 这样就可以初始化多个tuple的模板类
Foo<tuple<int, char>, tuple<double, float>> f;
// 获得参数包的长度
template<typename... Args>
void foo(Args&&... args){
int args_len = sizeof...(args);
}
std::atomic<T>
来声明一个原子变量std::atomic<T>
到T
的转换方便使用load()
, store()
和exchange()
三大成员函数, 这三种操作在其赋值操作符中广泛使用std::atomic_flag
, 其特点是无锁赋值, 因此没有上面的三大函数, 而是使用test_and_set()
和chear()
这两个原子操作进行读写, 经常用于制作自旋锁.std::atomic_flag
构造的时候是false的, test_and_set()
会将其原子地赋值为true, 然后chear()
将其改回false如何理解 C++11 的六种 memory order?- Furion W的回答 - 知乎 https://www.zhihu.com/question/24301047/answer/83422523 如何理解 C++11 的六种 memory order?- 知乎用户的回答 - 知乎 https://www.zhihu.com/question/24301047/answer/1193956492 What Do Each Memory Order Mean? https://stackoverflow.com/questions/12346487/what-do-each-memory-order-mean C++11中的内存模型下篇 - C++11支持的几种内存模型 https://www.codedump.info/post/20191214-cxx11-memory-model-2/
store()
, load()
)的第二个参数被传入, 通常表现为在原子操作函数周边设置的"内存栅栏"(用于清空流水线的一种指令)memory_order_relaxed
不对原子之间的顺序做任何要求, 一般用于单纯的原子计数器函数memory_order_seq_cst
这是默认的也是额外开销最高的内存模型, 这个选项不允许一切内存操作的重排, 确保了程序完全按照我们的要求执行memory_order_acquire
只能用在load操作中, 其使得该操作后的内存读写不能被重排到这个操作前, 相当于"读者"memory_order_release
类似, 只能用在store中, 其禁止这个操作前面的内存读写重排到这个操作之后, 相当于"写者"memory_order_consume
也只能用于load操作中, 其也禁止后面的重排, 但是这个限制只影响自己这个原子变量, 不会影响其他操作的重排. 这个模型比较少用, 很多编译器将其实现得和acquire相同memory_order_acq_rel
是获取-释放顺序, 是acquire和release的结合体, 起到一个内存栅栏的作用thread_local
来声明为TLS变量. 如int thread_local share_val;
terminate()
代表程序发生异常退出, 默认情况下内部调用了abort()
, 不过可以通过set_terminate()
来改变默认行为exit()
代表程序正常退出, 会自动调用变量的析构函数, 并调用atexit()
注册的函数abort()
代表无可挽回的程序终止, 不会调用任何析构函数, 操作系统直接结束掉这个进程, 可能导致很多交互中的进程的中间状态发生问题atexit()
可以注册多个函数, 退出的时候以与注册相反的顺序调用exit()
由于会调用析构的原因, 退出程序的时候可能需要卸除大量堆内存导致退出速度很慢, 但是这些堆内存本来都是可以交给操作系统一口气回收的. 且exit()
在多线程中还需要进行线程通信, 等待其他线程正常析构, 不正常的信号数据还可能导致死锁.quick_exit()
, 其不执行析构函数, 只是终止程序执行. 但不同于abort()
, 系统不会判断程序为错误退出因此不会进行额外的异常分析. quick_exit()
也可以用at_quick_exit()
注册一批函数在退出时调用, 标准要求至少支持32个C++ 位域 https://docs.microsoft.com/zh-cn/cpp/cpp/cpp-bit-fields?view=msvc-170 C/C++ 位域知识小结 https://www.cnblogs.com/zlcxbb/p/6803059.html
// 通过成员名后加冒号和位域结束的bit位来进行标记
// 每当到达一个字节长度就重新计数
// 一个位域必须存储在同一个字节中, 不能跨两个字节
// 故位域的长度不能大于一个字节的长度
// 类中自然可以混合使用多种位域, 因为本质是压缩储存
struct Date {
unsigned short nWeekDay : 3; // 0..7 (3 bits)
unsigned short nMonthDay : 6; // 0..31 (6 bits)
unsigned short nMonth : 5; // 0..12 (5 bits)
unsigned short nYear : 8; // 0..100 (8 bits)
};
nullptr
是"指针空值类型"的编译期常量关键字, 该类型命名为nullptr_t
, 即typedef decltype(nullptr) nullptr_t;
这是为了能给这个特殊的类型进行操作符重载(作为nullptr的特化)等设计alignof()
能返回目标的对齐字节, 返回值是size_t
. 很直观的, 类型不完整的类无法通过alignof的编译// 这里的Color按照8字节对齐, 但是结构体显然大多数时侯是一整个一整个地访问的
// 那么8字节的对齐并不能提高读写的效率, 也不能充分利用缓存
struct Color{ // alignof(Color): 8
double R; // alignof(double): 8
double G;
double B;
double A;
}
// 通过alignas我们可以重新设定类的对齐方式, 从而提高访问效率
struct alignas(32) Color{ // alignof(Color): 32
double R; // alignof(double): 8
double G;
double B;
double A;
}
alignas()
只能设定2的指数倍的对齐大小, 这是为了满足内部成员的对齐.alignof(std::max_align_t)
, 每个平台各自设定, 一般是16字节. 上面32字节对齐的设定称为扩展对齐, 可能会引起错误, 要谨慎std::align()
可以动态根据指定的对齐方式调整数据块的位置从而提高访问效率std::aligned_storage()
可以在产生对象实例的时候对对齐方式做出一定保证std::aligned_union()
则针对union处理, 选择类型中对齐最严格的一个值返回C++ 中的属性 https://docs.microsoft.com/zh-cn/cpp/cpp/attributes?view=msvc-170
[[noreturn]]
(C++11) 指定函数的控制流永远不会返回, 也就是一旦进入此函数必然引发异常, 不会回到调用者[[carries_dependency]]
(C++11) 既可以标识参数也可以标识函数本身(返回值), 表示此数据于多线程中没有数据依赖, 无需产生内存栅栏[[deprecated]]
(C++14) 此目标不适合使用, 将被废弃, 使用此目标会产生警告[[fallthrough]]
(C++17) 用于switch中, 表明此case没有break是预期内的行为, 只是一种提示[[nodiscard]]
(C++17) 目标的返回值不应放弃[[maybe_unused]]
(C++17) 当目标有意暂时不使用时, 令编译器不要发出警告[[likely]]
(C++20) 标记此分支为热代码, 提示编译器可以进行优化[[unlikely]]
(C++20) 标记此分支为冷代码, 提示编译器可以进行优化参数传递和命名约定 https://docs.microsoft.com/zh-cn/cpp/cpp/argument-passing-and-naming-conventions?view=msvc-170 C++ 的函数调用约定 https://blog.csdn.net/weixin_39731083/article/details/82533349
__cdecl
C和C++的默认调用约定, 参数从右向左入栈, 由调用者清理堆栈, 因此允许可变参数函数__stdcall
WINAPI
是它的别名, CALLBACK
也是它的别名, windows编程中很常用. 参数从右向左入栈, 函数自己清理堆栈, 因此不支持变长参数__fastcall
由寄存器传递开头的一小部分参数, 剩余参数从右向左入栈, 函数自己清理__thiscall
常用于类成员函数, 把this指针存入寄存器, 剩下的入栈, 函数自身清理. 如果参数不确定, 那么this指针在所有参数入栈后入栈, 调用者清理. 程序员不能使用这个调用约定