深⼊理解C++11
⽂章⽬录
整理 <<;深⼊理解C++11 C++11新特性解析与应⽤>> 笔记
2018年春晚第2章 保证稳定性和兼容性
final/override 控制
局部和匿名类型作模板实参struct Object { virtual void fun () = 0;}struct Ba : public Object { void fun () final ; // 声明为final }struct Derived : public Ba { void fun (); // ⽆法通过编译}
1
2
3
4
5
6
7
8
9struct Ba { virtual void Turing () = 0; virtual void Dijkstra () = 0; virtual void VNeumann (int g ) = 0; virtual void DKnuth () = 0; void Print ();}struct DerivedMid : public Ba { // void VNeumann(double g); // 接⼝被隔离,曾想多⼀个版本的VNeumann 函数}struct DerivedTop : public DerivedMid { void Turing () override ; void Dikjstra () override ; // ⽆法通过编译, 拼写错误,⾮重载 void VNeumann (double g ) override ; // ⽆法通过编译, 参数不⼀致,⾮重载 void DKnuth () const override ; // ⽆法通过编译,常量性不⼀致,⾮重载 void Print () override ; // ⽆法通过编译, ⾮虚函数重载}
1
2
3
4
5
6
7
8
9
10
11
12
会务是什么工作13
14
15
16
17
18
19
20
但是不能就地定义, 使⽤decltype获取类型
第3章 通⽤为本,专⽤为末
继承构造函数
情景: 在派⽣类中我们写的构造函数完完全全是为了构造基类,⽽基类有很多构造函数,那么我们需要为派⽣类写很多的"透传"构造函数.c++11 使⽤using声明继承基类的构造函数来解决这个重复写的问题.通过using A::A 的声明,把基类中的构造函数都继承到派⽣类B中.⽽且这是隐式声明的,意味这如果没有实际调⽤,编译器不会⽣成相关代码,节省了⽬标代码空间.
有的时候,会发⽣继承构造函数冲突问题,需要显⽰定义,阻⽌隐式⽣成相应的构造函数来解决冲突
注意:template <typename T > class X {};template <typename T > void TempFun (T t ) {}struct A {} a ;struct { int i ; } b ; // 匿名类型typedef struct { int t ; } B ;void Fun () { struct C {} c ; // 局部类型 X <A > x1; // c++98 通过, c++11 通过 X <B > x2; // c++98 错误, c++11 通过 X <C > x3; // c++98 错误, c++11 通过 TempFun (a ); // c++98 通过, c++11 通过 TempFun (b ); // c++98 错误, c++11 通过 TempFun (c ); // c++98 错误, c++11 通过}
1
2
34
埃及旅游5
6
7
8
9
10
11
12
购物天堂
13
14MyTemplate <struct { int a ; } > t ; // 编译错误,不能就地定义struct { int a ;}A ;MyTemplate <decltype (A )> t ; // 编译通过
1
2
3
4
5
6struct 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
11struct A { A (int ) {} };struct B { B (int ) {} };struct C : A , B { using A ::A ; using B ::B ; C (int ) {} // 显式定义}
1
2
3
4
5
6
7
8
1. 基类的构造函数是私有函数,不能继承
2. 使⽤了继承构造函数,那么不再为派⽣类⽣成默认构造函数.
委派构造函数
委派构造,就是指委派函数将构造的任务委派给委派构造函数(delegating constructor). 构造函数不能同时"委派"和使⽤初始化列表.初始化列表的初始化⽅式总是先于构造函数完成(实际在编译完成时已经决定了).
可以形成委派链,但是不能有委派环.
委派函数实际应⽤是使⽤构造模板函数产⽣⽬标构造函数, 委托构造函数使得泛型编程成为⼀种可能
右值引⽤:移动语义和完美转发
移动语义class Info { public : Info (): Info (1, 'a') {} Info (int i ): Info (i , 'a') {} Info (char e ): Info (1, e ) {} private : Info (int i , char e ): type (i ), name (e ) {} int type ; char name ;}
1
2
3
4
5
6
7
8
9
10
11#include <iostream>#include <deque>#include <vector>using namespace std ;class TDConstructed { template <class T > TDConstructed (T first , T cond ): 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
调⽤过程
添加移动构造函数
右值和左值的区别
可以取地址,有名字的是左值.不能取地址的,没有名字的是右值.
在C++11程序中,所有值必须属于左值,纯右值,将亡值三者之⼀.右值分为:纯右值,将亡值
纯右值: ⽤于辨认临时变量和⼀些不跟对象关联的值,如 1 + 3 产⽣的临时变量值
将亡值: 是C++11新增的跟右值引⽤相关的表达式.如T&&的函数返回值.#include <iostream>using namespace std ;class HasPtrMem { public : HasPtrMem (): d (new int (0)) { cout << "Construct: " << ++n_cstr << endl ; } HasPtrMem (const HasPtrMem & h ): d (new int (*h .d )) { cout << "Copy construct: " << ++n_cptr << endl ; } ~HasPtrMem () { cout << "Destruct: " << ++n_dstr << endl ; } int *d ; static int n_cstr = 0; static int n_cptr = 0; static int n_dstr = 0;};HasPtrMen getTemp () { return HasPtrMen (); }int main () { HasPtrMen a = getTemp ();}// c++ -std=c++11 test.cpp -fno-elide-constructors // 需要关掉编译器返回值优化选项(RVO, Return Value Optimization)
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
27Construct: 1Copy construct: 1Destruct: 1Copy construct: 2Destruct: 2Destruct: 3
1
2
3
4
5
6graph TD GetTemp(( ))--构造-->HasPrtMem HasPrtMem--拷贝构造-->HasPtrMem 临时对象HasPtrMem 临时对象--拷贝构造-->a
1
2
3
4 HasPtrMem (HasPtrMem && h ): d (h .d ) { h .d = nullptr ; // 将临时值的指针成员置空 cout << "Construct: " << ++n_mvtr << endl ; }
1
蝴蝶手工
2工作经验英文
3
4T ReturnRvalue () {}T && a = ReturnRvalue (); // 调⽤移动构造函数,为临时对象续命
1
2
注意: 右值引⽤不能绑定到左值上
注意 常量左值引⽤是⼀个"万能"的存在,只读不能修改
std::move 强制转化为右值std::move 从实现上讲,基本等同与⼀个类型转换static_cast<T&&>(lvalue
std::move 转化的左值不会⽴刻被析构
move() 使⽤场景
在移动构造函数中,为了保证成员变量也能使⽤移动构造函数,需要⽤到move()
移动语义其他问题上⾯的⽣命都会使得右值常量化,那么对临时变量的修改不能进⾏,⽆法实现移动语义.
在C++11中, 拷贝构造/赋值构造/移动构造/移动赋值 函数必须同时提供,或者同时不提供. ⼀旦提供⼀个,编译器将不会隐式⽣成其他三个.对与移动语义,抛异常是危险的,因为可能移动语义未完成,异常抛出,导致⼀些指针成悬挂指针,因此尽量写不抛出异常的移动构造函数,⼀旦有异常,直接终⽌程序, 添加 noexcept 关键字
完美转发int a ;int && b = a ; // 编译失败
1
2T ReturnRvalue () {}T & e = ReturnRvalue (); // 编译失败const T & f = ReturnRvalue (); // 编译通过T && g = ReturnRvalue (); // 编译通过
1
2
3
4
5#include <iostream>class Moveable { public : Moveable (): i (new int (3)) {} ~Moveable () { delete i ; } Moveable (const Moveable & m ): i (new int (*m .i )) {} Moveable (Moveable && m ): i (m .i ) { m .i = nullptr ; } int * i ;};int main () { Moveable a ; Moveable c (move (a )); // 调⽤移动构造函数 cout << *a .i << endl ; // 运⾏错误}
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Moveable { public : Moveable (Moveable && m ): i (m .i ), h (move (m .h )) { m .i = nullptr ; } int * i ; HugeMem h ;}
1
2
3
4
5
6
7
8Moveable (const Moveable &&);const Moveable ReturnVal ();
1
2