c++11_new
爱编程的大丙B站-c++11学习笔记
爱编程的大丙个人博客-c++11
alignas
alignas
1 2 3 4 5 6 7 8 9 10 11 12 13 struct alignas (32 ) test4 { char c; int i; double d; }; struct alignas (4 ) Info2 { uint8_t a; uint16_t b; uint8_t c; };
原始字面量
1 string str1 = R"(D:\hello\world\test.text)" ;
nullptr
C++ 程序 NULL 就是 0
C 程序 NULL 表示 (void*)0
C++ 中,void *
类型无法隐式转换为其他类型的指针
nullptr 无法隐式转换为整形,但是可以隐式匹配指针类型
constexpr
const 语义
变量只读,起始也是可变的,不能视为常量,不是在编译阶段计算
修饰常量,编译阶段计算
常量表达式的计算往往发生在程序的编译阶段
修饰函数
整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)
本质就是函数可以在编译阶段计算出结果,如果函数内部存在需要执行才能计算出结果的情况,则不能定义为常量表达式函数
修饰模板函数
根据传入和传出的值判断是否用这个关键字,编译阶段就判断传入的是不是常量
修饰构造函数
总结:就是告诉编译器可以在编译阶段就计算出结果
自动类型推导
auto
使用场景:
迭代器
泛型编程,返回值类型是根据传入的泛型决定的
decltype
使用场景:模板类中,如果传入的是STL容器,获取对应容器的迭代器
在定义迭代器的时候不能通过T::iterator
来定义,因为T是未知的,必须通过一个已知的类型才能定义一个变量
返回值类型后置
final、override
final
注意修饰位置在函数和类后面
修饰函数表示,这个方法不能在在子类中被重写了
修饰类表示,这个类不能被继承
override
用于确保重写的函数函数名不会出错,写的位置是在重写函数名后面
模板优化
模板的右尖括号
c++11以前,Base<vector<int>> b;
这种写法的>>
会被解析为右移
默认模板参数
类和函数都可,函数没有传入实参时,会使用默认类型和默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T = int , typename U = char >void func(T arg1 = 100 , U arg2 = 100 ){ cout << "arg1: " << arg1 << ", arg2: " << arg2 << endl ; } int main () { func('a' ); func(97 , 'a' ); func(); return 0 ; }
using的使用
using 语法和 typedef 一样,并不会创建出新的类型,它们只是给某些类型定义了新的别名
定义别名
using 相较于 typedef 的优势在于定义函数指针别名时看起来更加直观
模板的别名
委托构造函数和继承构造函数
https://subingwen.cn/cpp/construct/
委托构造函数
在构造函数中可以调用另外的构造函数,以此可精简代码
c++11之前是不支持构造函数中调用别的构造函数的
继承构造函数
c++11之前继承是没有继承构造函数的,要初始化,只能在子类中再调用父类的构造函数
继承构造函数就是可以允许子类继承父类的构造函数,直接可以初始化父类的成员变量
使用using Base::Base 、using Base::func
可直接使用父类的构造函数和被隐藏的函数func
列表初始化
聚合体和非聚合体的初始化
initializer_list ???
基于范围的for循环
原始for循环可以一边循环一边修改循环的内容,因为每次都会对结束判断
基于范围的for循环是一开始就确定了循环的范围,所以循环过程中是不能修改循环内容的,但是不用每次都判断是否结束了,效率更高
set的值和map的key在循环时本身就是不可变的
可调用对象 ???
Lambda 表达式
https://subingwen.cn/cpp/lambda/
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获)
拷贝的副本在匿名函数体内部是只读的
需要mutable修改,才能在函数内部可写
[=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量,同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
[this] - 捕获当前类中的 this 指针
可读写
让 lambda 表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项
右值引用
引用本质是别名
核心: 用右值引用接收一个将亡值就是给这个将亡值续命,相当于通过这个右值引用可以继续使用这个将亡值,完全继承了将亡值的所有权,这个将亡值可以是函数的返回值,本来返回后应该就析构了,但是因为有右值引用接收,所以并不会析构,直到右值引用不使用时才析构
初始化
初始化
右值引用和常量右值引用只能通过右值 初始化
左值引用不能通过右值初始化
常量左值引用可以通过很多种方式初始化,如下
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 #include <iostream> using namespace std ;int main () { int num = 5 ; int num2 = 6 ; int & a = num; int & y = 10 ; int && b = 10 ; const int && g = 10 ; const int && h = g; const int && i = b; int && j = b; const int & x = num; const int & c = 10 ; const int & e = a; const int & f = b; const int & d = c; const int & k = g; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> using namespace std ;int main () { int num = 5 ; int num2 = 6 ; const int & a = num; const int & b = a; num = 10 ; cout <<a<<endl ; cout <<b<<endl ; return 0 ; }
函数返回的临时对象处理 函数返回临时对象,外部接收时,直接是使用了函数内部构造的对象,地址都完全一样,这个过程没有调用拷贝构造和移动构造
即使发生NRV优化的情况下,Linux+ g++的环境是不管值返回方式还是引用方式返回的方式都不会发生拷贝构造函数,而Windows + VS2019在值返回的情况下发生拷贝构造函数,引用返回方式则不发生拷贝构造函数
反而是对返回的临时对象,使用move后再构造,会调用移动构造
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 #include <iostream> using namespace std ;class Test { public : Test() : m_num(new int (100 )) { cout << "construct: my name is jerry" << endl ; } Test(const Test& a) : m_num(new int (*a.m_num)) { cout << "copy construct: my name is tom" << endl ; } Test(Test&& a) : m_num(a.m_num) { a.m_num = nullptr ; cout << "move construct: my name is sunny" << endl ; } ~Test() { delete m_num; cout << "destruct Test class ..." << endl ; } int * m_num; }; Test getObj () { Test t; cout << "&t: " << &t << endl ; cout << "t.m_num: " << t.m_num << endl ; return t; } Test getObj1 () { return Test(); } int main () { Test t1 = getObj(); cout << "&t1: " << &t1 << endl ; cout << "t1.m_num: " << t1.m_num << endl ; cout <<"-------------------" <<endl ; Test t2 = getObj1(); cout << "t2.m_num: " << t2.m_num << endl ; cout <<"-------------------" <<endl ; Test t3 (getObj()) ; cout << "&t3: " << &t3 << endl ; cout << "t3.m_num: " << t3.m_num << endl ; cout <<"-------------------" <<endl ; Test t4 = move (getObj()); cout << "&t4: " << &t4 << endl ; cout << "t4.m_num: " << t4.m_num << endl ; cout <<"-------------------" <<endl ; Test&& t5 = getObj(); cout << "&t5: " << &t5 << endl ; cout << "t5.m_num: " << t5.m_num << endl ; cout <<"-------------------" <<endl ; Test&& t6 = getObj1(); cout << "&t6: " << &t6 << endl ; cout << "t6.m_num: " << t6.m_num << endl ; return 0 ; }; construct: my name is jerry &t: 0x7fffd883a718 t.m_num: 0x5570d17a7e70 &t1: 0x7fffd883a718 t1.m_num: 0x5570d17a7e70 ------------------- construct: my name is jerry t2.m_num: 0x5570d17a82a0 ------------------- construct: my name is jerry &t: 0x7fffd883a728 t.m_num: 0x5570d17a82c0 &t3: 0x7fffd883a728 t3.m_num: 0x5570d17a82c0 ------------------- construct: my name is jerry &t: 0x7fffd883a740 t.m_num: 0x5570d17a82e0 move construct: my name is sunnydestruct Test class ... &t4 : 0x7fffd883a730 t4.m_num: 0x5570d17a82e0 ------------------- construct: my name is jerry &t: 0x7fffd883a738 t.m_num: 0x5570d17a8300 &t5: 0x7fffd883a738 t5.m_num: 0x5570d17a8300 ------------------- construct: my name is jerry &t6: 0x7fffd883a740 t6.m_num: 0x5570d17a8320 destruct Test class ... destruct Test class ...destruct Test class ...destruct Test class ...destruct Test class ...destruct Test class ...
疑问??? 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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <iostream> using namespace std ;class Test { public : Test() : m_num(new int (100 )) { cout << "construct: my name is jerry" << endl ; } Test(const Test& a) : m_num(new int (*a.m_num)) { cout << "copy construct: my name is tom" << endl ; } Test(Test&& a) : m_num(a.m_num) { a.m_num = nullptr ; cout << "move construct: my name is sunny" << endl ; } ~Test() { if (m_num == nullptr ){ cout <<"m_num is nullptr" <<endl ; } delete m_num; m_num = nullptr ; cout << "destruct Test class ..." << endl ; } int * m_num; }; Test getObj () { Test t; cout << "&t: " << &t << endl ; cout << "t.m_num: " << t.m_num << endl ; return t; } Test getObj1 () { return Test(); } int main () { Test&& t7 = move (getObj()); cout << "&t7: " << &t7 << endl ; cout << "t7.m_num: " << t7.m_num << endl ; cout <<"-------------------" <<endl ; Test&& t8 = move (getObj1()); cout << "&t8: " << &t8 << endl ; cout << "t8.m_num: " << t8.m_num << endl ; return 0 ; }; construct: my name is jerry &t: 0x7ffc4e831420 t.m_num: 0x5638e2442e70 destruct Test class ... // 先析构 后面没有再析构 ????? &t7 : 0x7ffc4e831420 t7.m_num: 0 ------------------- construct: my name is jerry destruct Test class ... // 先析构 后面没有再析构 ????? &t8 : 0x7ffc4e831420 t8.m_num: 0
移动和完美转发 move
核心:move返回的是一个右值,用这个值初始化某个对象,就会调用这个对象的移动构造函数构造,如果没有移动构造函数则会调用拷贝构造函数构造
下面是自定义移动构造的情况
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <iostream> using namespace std ;class Test { public : Test() : m_num(new int (100 )) { cout << "construct: my name is jerry" << endl ; } Test(const Test& a) : m_num(new int (*a.m_num)) { cout << "copy construct: my name is tom" << endl ; } Test(Test&& a) : m_num(a.m_num) { a.m_num = nullptr ; cout << "move construct: my name is sunny" << endl ; } ~Test() { delete m_num; cout << "destruct Test class ..." << endl ; } int * m_num; }; int main () { Test t; cout <<"===================" <<endl ; Test&& t3 = move (t); cout << "t.m_num: " << *t.m_num << endl ; cout <<"t3.m_num: " << *t3.m_num << endl ; cout <<"===================" <<endl ; return 0 ; }; construct: my name is jerry =================== t.m_num: 100 t3.m_num: 100 =================== destruct Test class ... int main(){ Test t; cout <<"===================" <<endl ; Test t2 = move (t); cout <<"===================" <<endl ; cout << "t2.m_num: " << *t2.m_num << endl ; cout << "t.m_num: " << *t.m_num << endl ; cout <<"===================" <<endl ; return 0 ; }; construct: my name is jerry =================== move construct: my name is sunny =================== t2.m_num: 100 Segmentation fault (core dumped) int main(){ Test t; cout <<"===================" <<endl ; Test t2 = move (t); cout <<"===================" <<endl ; cout << "t2.m_num: " << *t2.m_num << endl ; cout <<"===================" <<endl ; return 0 ; }; construct: my name is jerry =================== move construct: my name is sunny=================== t2.m_num: 100 =================== destruct Test class ... destruct Test class ...
下面是没有自定义移动构造的情况
没有自定义移动构造就是用一个右值,调用拷贝构造去构造了一个新对象,和直接=赋值效果是一样的
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> using namespace std ;class Test { public : Test() : m_num(new int (100 )) { cout << "construct: my name is jerry" << endl ; } Test(const Test& a) : m_num(new int (*a.m_num)) { cout << "copy construct: my name is tom" << endl ; } ~Test() { delete m_num; cout << "destruct Test class ..." << endl ; } int * m_num; }; Test getObj () { Test t; return t; } int main () { Test t; cout <<"===================" <<endl ; Test t2 = move (t); Test t3 = t; cout <<"===================" <<endl ; cout << "t3.m_num: " << *t3.m_num << endl ; cout << "t2.m_num: " << *t2.m_num << endl ; cout << "t.m_num: " << *t.m_num << endl ; cout <<"===================" <<endl ; return 0 ; }; construct: my name is jerry =================== copy construct: my name is tom copy construct: my name is tom =================== t3.m_num: 100 t2.m_num: 100 t.m_num: 100 =================== destruct Test class ... destruct Test class ... destruct Test class ...
forward
当T为左值引用类型时,t将被转换为T类型的左值
当T不是左值引用类型时,t将被转换为T类型的右值,T为左值或左值引用也会被转换成右值
智能指针 auto_ptr
c++17后移除
问题:独占指针,但是可以赋值,赋值后再使用原来的指针则会报错
由new 创建堆区内容,auto_ptr指向这个堆区内容,在auto_ptr指针销毁时,他所指向的堆区也会自动被delete 掉。
所有权转移:不小心把它传递给另外的智能指针,原来的指针就不再拥有这个对象了。在拷贝/赋值过程中,会直接剥夺指针对原对象对内存的控制权, 转交给新对象,然后再将原对象指针置为nullptr
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <memory> #include <iostream> using namespace std ;int main () { auto_ptr <int > p1 (new int (10 )) ; auto_ptr <int > p2 = p1; cout << *p2 << endl ; return 0 ; }
unique_ptr
使用踩坑
unique_ptr是专属所有权所以unique_ptr管理的内存,只能被一个对象持有,不支持复制和赋值。
移动语义: unique_ptr禁止了拷贝语义,但有时我们也需要能够转移所有权于是提供了移动语义,即可以使用std.move()进行控制所有权的转移。
初始化
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 #include <iostream> #include <memory> using namespace std ;unique_ptr <int > func () { return unique_ptr <int >(new int (520 )); } int main () { unique_ptr <int > ptr1 (new int (10 )) ; cout << "ptr1: " << *ptr1 << endl ; unique_ptr <int > ptr2 = move (ptr1); cout << "ptr2: " << *ptr2 << endl ; unique_ptr <int > ptr3 = func(); cout << "ptr3: " << *ptr3 << endl ; ptr3.reset(new int (1024 )); cout << "ptr3: " << *ptr3 << endl ; return 0 ; }
踩坑
move后,用原来的指针也会报错,感觉和auto_ptr一样
用一个原指针通过move初始化两个指针,编译不出错,但执行出错
ptr1已经转移所有权给ptr2 ptr1为空指针 再转移所有权给ptr3 ptr3为空指针 执行出错
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> #include <memory> using namespace std ;class Test { private : int * a_; int b_; public : Test():a_(new int (0 )), b_(0 ){ cout <<this <<" :Test default constructor" <<endl ; } Test(int a, int b):a_(new int (a)), b_(b){ cout <<this <<" :Test constructor" <<endl ; } Test(const Test& t):a_(new int (*t.a_)), b_(t.b_){ cout <<this <<" :Test copy constructor" <<endl ; } Test(Test&& t):a_(t.a_), b_(t.b_){ cout <<this <<" :Test move constructor" <<endl ; t.a_ = nullptr ; t.b_ = 0 ; } ~Test(){ cout <<this <<" :Test destructor" <<endl ; delete a_; } void print () { cout <<"print: " <<"a: " <<*a_<<" b: " <<b_<<endl ; } }; int main () { unique_ptr <Test> ptr1 (new Test(1 ,2 )) ; cout <<"ptr1: " ; ptr1->print (); unique_ptr <Test> ptr2 = move (ptr1); unique_ptr <Test> ptr3 = move (ptr1); cout <<"ptr2: " ; ptr2->print (); return 0 ; } 0x55812a6afe70 :Test constructorptr1: print : a: 1 b: 2 ptr2: print : a: 1 b: 2 0x55812a6afe70 :Test destructor
shared_ptr c++11 STL emplace
源码分析
vec.push_back(Test()) 和 vec.emplace_back(Test())
一样, 都是临时对象一次构造一次析构,一次移动构造
vec.push_back(t) 和 vec.emplace_back(t)
一样,都是一次拷贝构造(不算已有对象的构造和析构),t是已经存在的对象
vec.emplace_back(8, 108)
的方式传入临时对象Test(8,108)
时, 才只调用一次有参构造, 这种方式才不同于push_back
, 效率更高
总结:只有传入临时对象,且是以构建对象的参数传入时,emplace_back
才会效率高于push_back
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <vector> using namespace std ;class Test { private : int * a_; int b_; public : Test():a_(new int (0 )), b_(0 ){ cout <<this <<" :Test default constructor" <<endl ; } Test(int a, int b):a_(new int (a)), b_(b){ cout <<this <<" :Test constructor" <<endl ; } Test(const Test& t):a_(new int (*t.a_)), b_(t.b_){ cout <<this <<" :Test copy constructor" <<endl ; } Test(Test&& t):a_(t.a_), b_(t.b_){ cout <<this <<" :Test move constructor" <<endl ; t.a_ = nullptr ; t.b_ = 0 ; } ~Test(){ cout <<this <<" :Test destructor" <<endl ; delete a_; } void print () { cout <<"print: " <<"a: " <<*a_<<" b: " <<b_<<endl ; } }; int main () { vector <Test> vec; for (int i = 0 ; i < 3 ; i++){ vec.push_back(Test(i, i + 100 )); } cout <<"vec.capacity: " <<vec.capacity()<<endl ; cout <<"vec.size: " <<vec.size ()<<endl ; cout <<"--------emplace_back----------" <<endl ; vec.emplace_back(8 , 108 ); }