深入理解C++ 11

1 保证稳定性和兼容性

1.1 保持与C99兼容(STDCHOSTED_, STDC, STDCVERSION__)
1.1.1 C99中的预定义宏
1.1.2 func预定义标识符
1.1.3 _Pragma操作符
1.1.4 不定参数宏定义以及 __VA_ARGS
1.1.5 宽窄字符串链接
1.2 long long 整型
1.3 扩展整型(signed char, short int, int, long int, long long int)
1.4 宏 __cplusplus 用于C和C++混合编写
1.5 静态断言

断言有助于快速定位违反了某些前提条件的程序错误, assert 只能在程序运行的时候才起作用。static_assert用于编译时候用的断言。

1.6 noexcept 修饰符和 noexcept 操作符

noexcept 表示修饰的函数不会抛出异常,如果抛出了异常,编译器直接调用std::terminate()终止运行。

1.7 快速初始化成员变量

C++ 11 中,标准允许非静态成员变量的多种初始化形式。具体而言,除了初始化列表之外, 还允许使用等号=或者{}进行就地的非静态成员变量的初始化, 通过花括号式的集合初始化列表。

1.8 非静态成员的sizeof

struct People {
public:
int hand;
static People *all;
}
支持 sizeof(People::hand), sizeof(People::all), sizeof((new People()).hand)

1.9 friend 友元

可以无视 public, protected, private的属性,友元类或者友元函数都可以访问

1.10 final/override 控制

final 关键字用于阻止函数继续重写,使派生类不可覆盖它所修改的虚函数。
如果派生类在虚函数声明时使用了virtual描述符,那么该函数就必须 override 重载基类中的同名函数,否则代码无法编译通过。

1.11 函数模板的默认模板参数

template class DefClass{}; int 就是默认参数
template <typename T, int i = 0> class DefClass{}; int 就是默认参数
为多个模板参数指定默认值时候,必须遵照从右到左的规定指定

1.12 外部模板

声明:extern templat void fun(int), 使用extern声明,不会再实例化代码,而是引用外部的模板声明。也可以把外部模板声明放在头文件中,这样所有包含test.h的头文件中就可以共享这个外部模板声明了。就好比全局变量的定义,外部声明和使用。

1.13 局部匿名类型作为模板实参

可以使用局部的结构体,变量,匿名的结构体和变量 传递给 模板类和模板函数

2. 通用为本,专用为末

2.1 继承构造函数

可以使用 using Base::Base 的方式把基类中的构造函数全部集成到派生类中。也可以通过 using Base::f 的方式,继承某一个具体的方法。在多个基类,继承构造函数冲突的时候,可以通过显示定义构造函数,阻止隐式生成响应的继承构造函数。

2.2 委托构造函数

原则上编译器不允许构造函数中调用构造函数。 Info(char e): Info() { name = ‘e’}, 也就是说,在初始化列表中进行构造,委派。

2.3 移动语义和完美转发(这块好trick,反正也用不到, 有uniptr替代)
2.3.1 指针成员和拷贝构造
1
2
3
4
5
6
7
8
9
class HasPtrMem {
public:
HasPtrMem(): d(new int(0)) {}
HasPtrMem(const HasPtrMem & h): d(new int(*h.d)) {}
~HasPtrMem() {
delete d;
}
int * d;
}

HashPtrMem a; HasPtrMem b(a); a.d 和 b.d 都指向了同一个内存,C++中被称为浅拷贝。

解决办法就是 去掉构造函数中的 const(关于这块,要继续了解const的作用, 正常操作,好像也不会加const吧)

2.3.2 移动语义
1
2
3
4
5
6
7
8
class HasPtrMem {
public:
HasPtrMem(): d(new int(3)) {}
HasPtrMem(const HasPtrMem &h): d(new int(*h.d)) {} // 注意这里还是const,也就是还是原来的内存,并没有新new
HasPtrMem(HasPtrMem && h) d(h.d) { // 移动构造函数
h.d = nullptr; // 将临时值的指针成员置空
}
}

因为移动构造完成之后,临时对象会立刻被析构,如果不改变 h.d,析构函数 会析构掉我们本来偷来的堆内存(那如果a = b的时候,也会发生移动构造吧,b的内存会被偷走,也就是说,这里其实也是坑)。

2.3.3 左值,右值,和右值引用
  1. 可以取地址的,有名字的,就是左值,不能取地址的,没有名字的,就是右值。
  2. T && a = ReturnValue(), ReturnValue 本来在返回右值之后,就将析构,而通过右值引用,有重获新生,生命周期将与a一样
  3. T b = ReturnValue(), b 只是由临时值构成的,而临时值在表示式结束之后,会多一次析构和构造的开销
  4. 右值引用不能绑定到任何的左值的。
  5. 常量左值引用是一个万能的引用类型,可以接受常量左值,非常量左值,右值进行初始化,但是其引用的右值在余生中只能是只读的。可以使用常量左值引用来减少临时变量的开销。
  6. std::move的作用,把一个左值强制成为右值, Copyable news = std::move(s), 如果s中包含一些一些大块内存指针,news 就可以把内存窃为己有(s自身不可以再使用)
2.3.5 移动语义的一些问题

通常情况下,如果需要移动语义,程序员就小自己定义移动构造函数

2.3.6 完美转发

完美转发是指:函数模板中,完全依照模板的参数类型,将参数传递给函数模板调用的另一个函数,比如 template void IamForwarding(T t) {IrunCodeActually(t)}, 这里产生了额外的对象拷贝,如果用常量左值(const T & t)来,则可能有无法接受常量左值的函数存在
template void PerfectForward(T && t) { RunCode(forward(t))}

2.4 显式转换操作符

explicit 禁止隐式转换

2.5 列表初始化
  1. int a[] = {1, 2,3}
  2. int b[] {1, 2, 3}
  3. vectorc {1, 2, 3}
  4. 可以通过 initializer_list使自定义的类使用列表初始化
  5. POD 类型, Plain Old Data,平凡数据,默认的构造函数,析构函数,默认的拷贝构造和移动构造函数,拷贝赋值运算符和移动赋值运算符,没有虚函数和虚基类,标准布局,没有privat
  6. 内联名字空间 允许在父空间定义或者特化子空间名字的模板, inline 相当于 将名字空间导入到父空间中
  7. 可以用typedef 和 using 来定义模板和域的别名, using uint = unsigned int;

3 新手易学,老兵易用

3.1 右尖括号 > 的改进

C++98中,如果实例化模板的时候,出现了连续两个 >, 中间应该用空格隔开,以免编译错误,C++ 11 会要求编译器智能的判断哪些情况下>>不是右移符号。 X< 1>> 5>x, C++98中, 会认为 >> 是位移符号,最终得到 X<0>x, 而在C++ 11中会得到一个编译错误的警告,将第一个> 与 X之后的<匹配, 可以通过将 1 >> 5括起来

3.2 auto类型推导

auto声明变量的类型,必须由编译器在编译时期推导而得,可以将复杂的变量声明简化,增加可读性(存疑,理解代码时候,需要自己推导类型),能够自适应,一定程度上支持泛型的编程。

  1. auto 并不能从初始表达式中保留CV限制符
  2. auto 并不能作为形参
  3. auto i = 1, j = 4.13f, 编译失败, auto 从左到右推导 i 为int 所以j 也定义为int,标准称:auto是将要被推导出的类型的占位符。
  4. 对结构体来说,非静态成员的类型不能是auto,不能声明auto 数组
  5. 不能实例化时候,使用auto作为模板参数
3.3 typeid 与 decltype
3.3.1 RTTI,运行时类型识别

在C++11中,增加了 hash_code,返回该类型唯一的哈希值,以供程序员对变量的类型随时进行比较。((typeid(a).hash_code() == typeid(c).hash_code()),RTTI会带来运行时的开销,所以一些编译器会选择性的关闭该特性。

3.3.3 decltype 推导四原则
  1. 如果e是一个没带括号的标记符表达式,或者类成员访问表达式,那么decltype(e) 就是e所命名的实体的类型,如果e是一个被重载的函数,会导致编译时错误
  2. 如果e的类型是T,如果e是一个讲亡值,那么decltype(e)为T&&
  3. 如果e的类型是T,如果e是一个左值,则decltype(e)为T&
  4. 如果e的类型是T,则decltype(e)为T
    3.3.4 如果对象的定义中有const或者volatile限制符,使用decltype进行推导时,其成员(结构体内部变量)不会继承const或volatile限制符
    3.5 基于范围的for循环
    for_each(arr, arr + sizeof(arr) / sizeof(arr[0]), action1)
    for (int & a: arr) {}
    如果迭代变量在循环中不会被修改,完全可以不用引用的方式来做迭代变量

4. 提高类型安全

4.1 强枚举类型 enum class Type
  1. 强作用域,不会被输出到父空间作用域
  2. 转换限制,不可以与整型隐式的互相转换
4.2 智能指针和垃圾回收
  1. unique_ptr 与所指对象的内存紧密绑定,不能与其他unique_ptr类型的指针共享所指的内存,所有权仅能通过move来转移,一旦转移成功,原来的unique_ptr就失去了对象内存的所有权。
  2. shared_ptr 允许多个智能指针共享的拥有同一内存, 调用reset只会降低引用计数,而不会释放,只有在引用计数为0的时候,才会释放所占的堆内存的空间。
  3. weak_ptr 可以指向shared_ptr指针所指向内存,而不拥有该内存,使用lock可以返回其指向内存的shared_ptr对象,如果堆存对象无效的时候,返回空指针。