稳定性与兼容性 断言 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifdef NDEBUG #define assert(expr) (static_cast<void> (0)) #else #endif #ifndef _COMPLEX_H #error "Nerver use <bits/cmathcalls.h> directly; include <complex.h> instead." #endif #define assert_static(e) \ do { \ enum { assert_static__ = 1/(e) }; \ } while (0)
异常 如果noexcept修饰的函数抛出了异常,编译器可用选择直接调用std::terminate()函数来终止程序的运行,如析构函数不应该抛出异常,所以默认是noexcept(true)。在c++98中,使用throw()来声明不抛出异常的函数。
1 2 template <class T >void fun () noexcept (noexcept (T())) {}
初始化 初始化列表的效果总是优先于就地初始化的。 非常量的静态成员变量,需要在头文件以外定义,这会保证编译时,类静态成员的定义最后只存在于一个目标文件中。
sizeof 1 2 3 4 5 6 7 struct People {public : int hand; static People * all; }; sizeof (((People*)0 )->hand); sizeof (People::hand);
友元 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Poly ;typedef Poly P;class LiLei { friend class Poly ; }; class LiLei { friend Poly; }; class LiLei { friend P; }; class P ;template <typename T>class People { friend T; };
final/override 如果不想成员函数被重载,可以直接将成员函数定义为非虚的。final通常只在继承关系的中途终止派生类的重载有意义。 派生类在虚函数声明中使用了override描述符,那么该函数必须重载其基类中的同名函数,否则编译不过。
模板函数的默认模板参数 1 2 3 4 5 void DefParm (int m = 3 ) {} template <typename T = int > class DefClass {};template <typename T = int > void DefTempParm () {};
不按照从右往左定义默认类模板参数的模板类都无法通过编译,而函数模板默认模板参数位置则比较随意。
外部模板 可以使用强制实例化进行外部声明。 不使用外部模板声明并不会导致问题,因为只是代码重复,不是数据重复,这是一种对编译时间和空间的优化手段。
通用特性 继承构造函数 如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。
1 2 3 4 5 6 7 8 9 10 11 struct A { A(int i) {} A(double d, int i) {} A(float f, int i, const char * c) {} }; struct B : A { using A::A; virtual void ExtraInterface () {} };
参数默认值会导致多个构造函数版本的产生。
1 2 3 4 5 6 7 8 9 10 11 12 struct A { A (int a = 3 , double b = 2.4 ) {} }; struct B : A { using A::A; };
可以通过显示定义继承类的冲突的构造函数,阻止隐式生成相应的继承构造函数来解决冲突。
1 2 3 4 5 6 7 8 struct A { A (int ) {} };struct B { B (int ) {} };struct C : A, B { using A::A; using B::B; C (int ) {} };
一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了。
1 2 3 4 struct A { A (int ) {} };struct B : A { using A::A; };B b;
委派构造函数 黑客版:
1 2 3 Info () { InitRest (); }Info (int i) { new (this ) Info (); type = i; } Info (char e) { new (this ) Info (); name = e; }
所谓委派构造,指的是委派函数将构造的任务委派给了目标构造函数来完成的类构造方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Info {public : Info () { InitRest (); } Info (int i) : Info () { type = i; } Info (char e) : Info () { name = e; } private : void InitRest () { } int type {1 }; char name {'a' }; }; class Info {public : Info () : Info (1 , 'a' ) { } Info (int i) : Info (i, 'a' ) { } Info (char e) : Info (i, e) { } private : Info (int i, char e) : type (i), name (e) { } int type; char name; };
目标构造函数的执行总是先于委派构造函数,如果委派构造函数中使用try,从目标构造函数中产生的异常,都可以在委派构造函数中被捕捉到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class DCExcept {public : DCExcept (double d) try : DCExcept (1 , d) { cout << "Run the body." << endl; } catch (...) { cout << "caught exception" << endl; } private : DCExcept (int i, double d) { cout << "going to throw" << endl; throw 0 ; } int type; double data; };
范型编程版:
1 2 3 4 5 6 7 8 9 10 class TDConstructed { template <class T> TDConstructed (T first, T last) : l(first, last) { } list<int > l; public : TDConstructed (vector<short > &v): TDConstructed (v.begin (), v.end ()) {} TDConstructed (deque<int > &d): TDConstructed (d.begin (), d.end ()) {} };
右值引用 移动语义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class HasPtrMem {public : HasPtrMem () : d (new int (0 )) { cout << "Construct: " << ++n_str << endl; } HasPtrMem (const HasPtrMem &h) : d (new int (*h.d)) { cout << "Copy constructor: " << ++n_str << endl; } ~HasPtrMem () { count << "Destruct: " << ++n_dstr << endl; } int *d; static int n_cstr; static int n_dstr; static int n_cptr; }; int HasPtrMem::n_cstr = 0 ;int HasPtrMem::n_dstr = 0 ;int HasPtrMem::n_cptr = 0 ;HasPtrMem GetTemp () { return HasPtrMem (); }int main () { HasPtrMem a = GetTemp (); }
移动构造函数:
1 2 3 4 HasPtrMem (HasPtrMem &&h) : d (h.d) { h.d = nullptr ; cout << "Move construct: " << ++n_mvtr << endl; }
由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
1 2 3 T && a = ReturnRvalue (); T b = ReturnRvalue (); const T &f = ReturnRvalue ();
std::move并不移动任何东西,只是将一个左值强制转化为右值引用,继而通过右值引用使用该值,以用于移动语义。
1 static_cast <T&&>(lvalue)
移动语义一定是要修改临时变量的值,以下会使得临时变量常量化,成为一个常量右值,使得临时变量的引用也无法修改,导致无法实现移动语义。
1 2 Moveable (const Moveable&&)const Moveable ReturnVal () ;
应该尽量编写不抛出异常的移动构造函数。std::move_if_noexcept在类的构造函数没有noexcept关键字修饰时返回一个左值引用使变量可以实现拷贝语义。
1 2 3 4 5 6 7 8 9 10 11 12 struct Nothow { Nothow () {} Nothow (Nothow&&) noexcept { std::cout << "Nothow move constructor" << std::endl; } Nothow (const Nothow&) { std::cout << "Nothow move constructor" << std::endl; } }; Nothrow n; Nothrow nt = move_if_noexcept (n);
完美转发 引用折叠规则:一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用。
1 2 3 typedef const int T;typedef T& TR;TR& v = 1 ;
完美转发实现过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void IamForwording (X& &&t) { IrunCodeActually (static_cast <X& &&>(t)); } void IamForwording (X& t) { IrunCodeActually (static_cast <X&>(t)); } void IamForwording (X&& &&t) { IrunCodeActually (static_cast <X&& &&>(t)); } void IamForwording (X&& t) { IrunCodeActually (static_cast <X&&>(t)); } template <typename T>void IamForwording (T&& t) { IrunCodeActually (forward(t)); }
列表初始化 只要#include了头文件,并且声明了一个以initialize_list模板类为参数的构造函数,就可以使得自定义的类可以使用列表初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <vector> #include <string> using namespace std;enum Gender { boy, girl };class People {public : People (initializer_list<pair<string, Gender>> l) { auto i = l.begin (); for (; i != l.end (); i++) { data.push_back (*i); } } private : vector<pair<string, Gender>> data; }; People ship2012 = {{"Garfield" , boy}, {"HelloKitty" , girl}};
列表初始化是唯一一种可以防止类型收窄的初始化方式。
1 2 3 float f{7 }; int g{2.0f }; A obj2{};
POD类型的好处
字节赋值,可以使用memset和memcpy对POD类型进行初始化和拷贝操作。
提供对C内存布局兼容。
保证了静态初始化的安全有效,比如放入目标文件的.bss段,在初始化中直接被赋为0。
易用易学 auto 1 2 3 4 5 6 7 8 9 float radius = 1.7e10 ;PI pi; auto circumference = 2 * (pi * radius); #define Max1(a, b) ((a) > (b) ? (a) : (b)) #define Max2(a, b) ({ \ auto _a = (a); \ auto _b = (b); \ (_a > _b ? _a : _b; )})
如果要使得auto声明的变量是另一个变量的引用,必须使用auto &。
1 2 3 const double a;auto d = a; auto & e = a;
声明为引用或指针的auto变量可以带走其对象的相同属性,包括const、volatile. auto可以用来声明多个变量类型,不过这些变量的类型必须相同,否则编译报错。
1 auto o = 1 , &p = o, *q = &p;
不能推导的情况:
auto不能为函数形参类型。
结构体非静态成员变量类型不能是auto。
不能声明auto数据。
vectorv 不行。
decltype 与auto相同,decltype类型推导也是在编译时进行的。 decltype一个最大的用途就是用在追踪返回类型的函数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 template <typename T1, typename T2>void Sum (T1 &t1, T2 &t2, decltype (t1 + t2) &s) { s = t1 + t2; } void Sum (int a[], int b[], int c[]) { } int main () { int a[5 ], b[5 ], c[5 ]; Sum (a, b, c); int d, e, f; Sum (d, e, f); }
1 2 3 4 5 6 7 8 #include <type_traits> using namespace std;typedef double (*func) () ;int main () { result_of<func ()>::type f; }
自动追踪返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 template <typename T1, typename T1>auto Sum (T1 &t1, T2 &t2) -> decltype (t1 + t2) { return t1 + t2; } int (*(*pf ())())() { return nullptr ; } auto pf1 () -> auto (*) () -> int (*) { return nullptr ; } template <typename T>auto Forward (T t) -> decltype (foo(t)) { return foo (t); }
类型安全 强类型枚举 1 2 3 4 5 6 7 #define Male 0 #define Female 1 enum { Male, Female };const static int Male = 0 ;const static int Femal = 1 ;