C++11新特性详解

c++ c++ 1475 人阅读 | 0 人回复

发表于 2022-11-25 23:10:50 | 显示全部楼层 |阅读模式

看完这篇读书笔记,希望可以理解:
1.auto
  • 当不声明为指针或者引用时,表达式带有const(volatile)时,auto会把const(volatile)抛弃
  • 当声明为指针或者引用时,auto的表达结果保留const(volatile)
  • auto不能用于函数参数、非静态成员变量、数组、模版参数
2.decltype
int x = 0; decltype(x) y = 1;
decltype精确的推导出表达式定义的本身类型,并且重用,不会去掉cv属性
decltype(exp)推导规则:
  • exp是标识符、类访问表达式,decltype(exp)与exp的类型一致
  • exp是函数调用,decltype(exp)和返回类型一致
  • exp是一个左值,decltype(exp)是exp类型的左值引用 int n,m = 0; decltype(n+=m) d = c;//d->int &
  • 其他情况decltype(exp)均与exp类型一致

第一种修改
用auto
std::decay:移除引用和CV符,保留指针。
3.using:using的别名语法覆盖了typedef的全部功能
4.函数模板的默认模板参数的支持
5.for
std::vector<int> arr;
  • for(auto it = arr.begin(); it != arr.end(); ++it)//it是迭代器 xx->first
  • std::foreach(arr.begin(), arr.end(), do_cout);//void do_cout(int n){ std::cout<<n<<endl;}
  • foreach(auto it in arr)
  • for(auto n : arr)// 只读
  • for(auto& n : arr)//可以修改//n 类型是容器中的value_type,不是迭代器(xx.first)
  • for(auto val : get_range())//get_range()只调用一次 冒号后面的表达式只调用一次
6.lambda表达式
[capture] (params) opt->ret{body;};
capture是捕获列表;params是参数表;opt是函数选项;ret是返回值类型;body是函数体
  1. auto f = [](int a)->int { return a+1; };//f(1) => 2
  2. -------------------------------------------------
  3. int a= 0;
  4. auto f = [=]{ return a; };
  5. a += 1;
  6. std::cout<<f()<<std::end;//a = 0,在捕获的一瞬间就被复制了
复制代码

(1)lambda表达式可以通过捕获列表捕获一定范围内的变量:
  • []不捕获任何变量,可以转换为普通的函数指针,但是捕获变量的就不可以了
  • [&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获),可以改变捕获值
  • [=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获),所以不会改变捕获值
  • [=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量(函数方法名)
  • [bar]按值捕获bar变量,同时不捕获其他变量(按变量名称)
  • [this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了& =,就默认添加此选项。捕获this的目的是可以在lambda中使用当前类的成员函数和成员变量
(2)lambda表达式优势:就地匿名函数不需要定义函数对象,简化了调用;在合适的地方增加闭包,使程序灵活。
7.tuple:等价与一个结构体
8.右值引用T&&
(1)左值与右值
  • 左值:表达式结束后依然存在持久对象
  • 右值:表达式结束时不再存在临时对象 纯右值+将亡值
  • 区分的方法:看能否对表达式取地址,如果能为左值,否则为右值
  • 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式都是纯右值
  • 将亡值:将要被移动的对象,T&&函数返回值、std::move返回值和转换为T&&的类型的转换函数的返回值
常量左值引用是万能的引用,可以接受左值、右值、常量左值、常量右值;非常量左值引用只接受左值。
(2)C++11的折叠规则:
  • 所有右值引用叠加到右值引用上仍然还是右值引用
  • 其他的叠加都将变成左值引用
(3)右值引用优化性能,避免深拷贝
  • 移动构造:
  1. class A
  2. {
  3. public:
  4.           A():m_ptr(new int(0)){ cout<<"construct"<<endl; }
  5.           A(const A&a):m_ptr(new int(*a.mptr)){ cout<<"copy construct"<<endl; }
  6.           A(A&& a):m_ptr(a.m_ptr){ a.m_ptr = nullptr; cout<<"move construct"<<endl; }
  7.           ~A() { delete m_ptr; }
  8. private:
  9.          int * m_ptr;
  10. }
复制代码

  • 移动语义:A&&根据参数左值还是右值来建立分支,如果是临时值,则会选择移动构造函数,移动构造函数只对临时对象资源做了浅拷贝,避免了额外的拷贝。
  1. class MyString
  2. {
  3. private:
  4. char* m_data;
  5. size_t m_len;
  6. void copy_data(const char* s);//深拷贝
  7. public:
  8. //....
  9. MyString(const MyString& str){
  10. m_len = str.m_len;
  11. copy_data(str.m_data);
  12. }
  13. MyString &operator=(const MyString & str)
  14. {
  15. if(this != &str)
  16. {
  17. m_len = str.m_len;
  18. copy_data(str.m_data);
  19. return *this;
  20. }
  21. }
  22. MyString(MyString&& str)//移动构造
  23. {
  24. m_len = str.m_len;
  25. m_data = str.m_data;
  26. str.m_len = 0;
  27. str.m_data = NULL;
  28. }
  29. MyString & operator = (MyString&& str)//移动赋值
  30. {
  31. if(this != &str)
  32. {
  33. m_len = str.m_len;
  34. m_data = str.m_data;
  35. str.m_len = 0;
  36. str.m_data = NULL;
  37. }
  38. }
  39. }
复制代码

  • move语义
std::move将左值转换为右值。只是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。

深拷贝和move的区别
  • 完美转发:指在函数模板中,完全依照模板的参数的类型将参数传递给函数模板中调用的另一个函数。std::forward(),完美转发实现了参数在传递过程中保持其值属性的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。
9.emplace_back VS push_back:emplace_back可通过参数构造对象,不需要拷贝或者移动内存,因此对象必须有对应的构造函数
10.无序容器:map set 内部实现是红黑树,无序容器unordered_map/unordered_multimap unordered_set/unordered_multiset是hash,不通过排序来处理元素,效率更高。不过对于自定义key时,需要提供hash函数和比较函数
11.C++11解决内存泄露问题
  • 垃圾回收方式总结
(1)引用计数:当对象引用次数变为0,可以被视作需要回收。优势:不会造成程序暂停,计数加减与引用计数紧密联系。劣势:“环形引用”可能造成内存泄漏。
(2)跟踪处理:
  • 标记-清除:程序中正在使用的对象是根对象,从根对象开始查找他们所引用的堆空间,并标记;未被标记的对象和非根对象就是垃圾,第二部清除阶段会清除掉。
  • 标记-整理:算法与标记清除一样,只是标记的对象向左靠齐,解决了内存碎片问题。
  • 标记-拷贝:堆空间分为from to。开始从from分配内存,from分配满了开始垃圾回收,把活对象拷贝到to的堆空间,这时候from里面都是垃圾,to里面是紧凑排列的,然后将from to角色交换。
(1)shared_ptr:每一个shared_ptr拷贝都指向相同内存。shared_ptr<int> p(new int(1));
注意:
  • 不要用一个原始指针初始化多个shared_ptr
  • 不要在函数实参中创建shared_ptr
  • 通过shared_from_this()返回this指针,不要将this指针作为shared_ptr返回出来,会导致重复析构
  1. class A:public std::enable_shared_from_this<A>{        
  2.       std::shared_ptr<A> GetSelf() { return shared_from_this(); }
  3. }
复制代码

  • 避免循环引用。循环引用会造成内存泄漏
  1. struct A{
  2. std::shared_ptr<B> bptr;
  3. };
  4. struct B{
  5. std::shared_ptr<A> aptr;
  6. };
  7. void TestPtr()
  8. {
  9. std::shared_ptr<A> ap(new A);
  10. std::shared_ptr<B> bp(new B);
  11. ap->bptr = bp;
  12. bp->aptr = ap;
  13. }
复制代码

(2)unique_ptr:独占型智能指针,不允许其他的智能指针共享内部指针,不可以通过赋值将一个unique_ptr赋值给另一个unique_ptr。可以通过std::move将一个指针所有权转移。
  • unique_ptr 可以指向一个数组 shared_ptr不可以
std::unique_ptr<int []> ptr(new int[10])
  • 删除器unique_ptr不可以直接指定删除器,需要确定删除器类型
  1. std::shared_ptr<int> ptr(new int(1), [](int *p){ delete p; });
  2. std::unique_ptr<int> ptr(new int(1), [](int *p){ delete p; });//error
  3. std::unique_ptr<int, void(*)(int *)> ptr(new int(1), [](int *p){ delete p; });
  4. std::unique_ptr<int, void(*)(int *)> ptr(new int(1), [&](int *p){ delete p; });//error
复制代码

(3)weak_ptr:主要用于监视shared_ptr,不会使引用计数加1,不可以共享指针,不可以操作资源。
基本用法:
  • use_count()获得观测资源的引用计数
  • expired()判断观测资源是否已经被释放
  • lock()获取监视的shared_ptr,可以用weak_ptr lock()获取this指针,不会发生引用计数加减和析构
  • 解决循环引用问题
  1. struct A{
  2. std::shared_ptr<B> bptr;
  3. };
  4. struct B{
  5. std::weak_ptr<A> aptr;
  6. };
  7. void TestPtr()
  8. {
  9. std::shared_ptr<A> ap(new A);
  10. std::shared_ptr<B> bp(new B);
  11. ap->bptr = bp;
  12. bp->aptr = ap;
  13. }
复制代码

(4)通过智能指针管理第三方库分配内存
  1. #define GUARD() std::shared_ptr<void> p##p(p, [](void*p){GetHandle()->Release(p);})
  2. void* p = GetHandle()->Create();
  3. GUARD(p);
  4. 或者
  5. void* p = GetHandle()->Create();
  6. std::shared_ptr<void> sp(p, [this](void*p){GetHandle()->Release(p);});
复制代码

12.final/override
  1. struct Object{
  2. virtual void fun() = 0;
  3. virtual void funa() = 0;
  4. };
  5. struct Base:public Object{
  6. void fun() final;
  7. };
  8. struct Derived:public Base{
  9. void fun();//无法通过编译,fun()不可以重写
  10. void funb() override;//funb()不是重载函数
  11. };
复制代码

13.=delete/=default
(1)默认函数:
  • 构造函数
  • 拷贝构造函数
  • 拷贝赋值函数(operator=)
  • 移动构造函数
  • 移动拷贝函数
  • 析构函数
(2)编译器还会为这些自定义类型提供全局默认操作符函数:
  • operator,
  • operator &
  • operator &&
  • operator *
  • operator ->
  • operator ->*
  • operator new
  • operator delete
(3)显示避免一些不必要的隐含类型
  1. class ConvType{
  2. public:
  3. ConvType(int i){};
  4. ConvType(char c) = delete;
  5. };
  6. void Func(ConvType ct){}
  7. void TestConv()
  8. {
  9. Func(3);
  10. Func('a');//编译不过
  11. ConvType ci(3);
  12. ConvType cc('a');//编译不过
  13. }
复制代码

(4)C++11提案中 explicit关键字何显示删除不要合用
防止类构造函数的隐式类型转换
  1. <pre style="overflow-wrap: initial; background: rgb(246, 246, 246); border-radius: 4px; font-size: 0.9em; overflow: auto; padding: calc(0.888889em); word-break: initial; color: rgb(18, 18, 18);"><code class="language-cpp" style="background-color: inherit; border-radius: 0px; font-family: Menlo, Monaco, Consolas, &quot;Andale Mono&quot;, &quot;lucida console&quot;, &quot;Courier New&quot;, monospace; font-size: inherit;"><span class="k" style="font-weight: 600;">class</span> <span class="nc" style="font-weight: 600; color: rgb(23, 81, 153);">ConvType</span><span class="p">{</span>
  2. <span class="k" style="font-weight: 600;">public</span><span class="o" style="font-weight: 600;">:</span>
  3. <span class="n">ConvType</span><span class="p">(</span><span class="kt" style="font-weight: 600; color: rgb(23, 81, 153);">int</span> <span class="n">i</span><span class="p">){};</span>
  4. <span class="k" style="font-weight: 600;">explicit</span> <span class="nf" style="font-weight: 600; color: rgb(241, 64, 60);">ConvType</span><span class="p">(</span><span class="kt" style="font-weight: 600; color: rgb(23, 81, 153);">char</span> <span class="n">c</span><span class="p">)</span> <span class="o" style="font-weight: 600;">=</span> <span class="k" style="font-weight: 600;">delete</span><span class="p">;</span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);">//删除explicit的char构造
  5. </span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);"></span><span class="p">};</span>
  6. <span class="kt" style="font-weight: 600; color: rgb(23, 81, 153);">void</span> <span class="nf" style="font-weight: 600; color: rgb(241, 64, 60);">Func</span><span class="p">(</span><span class="n">ConvType</span> <span class="n">ct</span><span class="p">){}</span>
  7. <span class="kt" style="font-weight: 600; color: rgb(23, 81, 153);">void</span> <span class="nf" style="font-weight: 600; color: rgb(241, 64, 60);">TestConv</span><span class="p">()</span>
  8. <span class="p">{</span>
  9. <span class="n">Func</span><span class="p">(</span><span class="mi" style="color: rgb(5, 109, 232);">3</span><span class="p">);</span>
  10. <span class="n">Func</span><span class="p">(</span><span class="sc" style="color: rgb(241, 64, 60);">'a'</span><span class="p">);</span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);">//可通过编译 因为发生了一次隐式的从char转换为int
  11. </span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);"></span> <span class="n">ConvType</span> <span class="n">ci</span><span class="p">(</span><span class="mi" style="color: rgb(5, 109, 232);">3</span><span class="p">);</span>
  12. <span class="n">ConvType</span> <span class="n">cc</span><span class="p">(</span><span class="sc" style="color: rgb(241, 64, 60);">'a'</span><span class="p">);</span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);">//编译不过
  13. </span><span class="c1" style="font-style: italic; color: rgb(153, 153, 153);"></span><span class="p">}</span></code></pre>
复制代码
(5)避免堆上分配class对象
  1. class ConvType{
  2. public:
  3. void* operator new(std::size_t) = delete;
  4. };
  5. void Func(ConvType ct){}
  6. void TestConv()
  7. {
  8. ConvType * a = new ConvType;//编译失败
  9. }
复制代码

(6)在指定内存位置为对象分配内存,不需要析构函数来完成一些对象级别的清理
  1. class ConvType{
  2. public:
  3. ~ConvType() = delete;
  4. };
  5. void Func(ConvType ct){}
  6. void TestConv()
  7. {
  8. ConvType a;//编译失败
  9. new (a) ConvType();
  10. }
复制代码

14.原子操作
(1)C++11原子数据类型:不需要显示声明互斥锁或者调用枷锁解锁API,线程就可以对便利互斥的访问。
  1. #include <atomic>
  2. #include <thread>
  3. #include <iostream>
  4. using namespace std;
  5. atomic_llong total {0};//原子数据类型
  6. void func(int){
  7. for(long long i = 0; i < 100000000LL; ++i){
  8. total += i;
  9. }
  10. }
  11. int main()
  12. {
  13. thread t1(func, 0);
  14. thread t2(func, 0);
  15. t1.join();
  16. t2.join();//999999900000000
  17. return 0;
  18. }
复制代码

(2)atomic_flag:通过test_and_set(将lock置为true)以及clear(将lock置为false)实现自旋锁。下面的例子里,直到clear执行,f()才开始调用start work,实现了t1等待t2线程。
(3)memory_order
前情提要:
  • 弱顺序内存模型:可以使得处理器进一步发掘指令中的并行性,使得指令执行的性能更高。
  • 只关心读写操作的执行顺序:由处理器的设计决定的,通常,处理器总是从内存中读出数据进行运算,再将运行结果返回内存,内存中的数据是一个准绳。寄存器中都是临时的。
  • atomic的操作增加了大量的内存栅栏,阻止编译优化,因此性能不好。
对应表里的枚举值看下面的例子:
  • release-acquire
  1. #include <atomic>
  2. #include <thread>
  3. #include <iostream>
  4. using namespace std;

  5. atomic<int> a;
  6. atomic<int> b;
  7. void Thread1(int){
  8. int t = 1;
  9. a.store(t, memory_order_relaxed);
  10. b.store(t, memory_order_release);
  11. }

  12. void Thread2(int){
  13. while(b.load(memory_order_acquire) != 2);
  14. cout<<a.load(memory_order_relaxed)<<endl;
  15. }

  16. int main()
  17. {
  18. thread t1(Thread1, 0);
  19. thread t2(Thread2, 0);
  20. t1.join();
  21. t2.join();//999999900000000
  22. return 0;
  23. }
复制代码

  • require-consume:只能保证原子操作发生在与ptr有关的原子操作之前,相比于前面的例子,先于发生关系弱化了
  1. #include <atomic>
  2. #include <thread>
  3. #include <iostream>
  4. using namespace std;

  5. atomic<string*> ptr;
  6. atomic<int> data;
  7. void Producer(){
  8. string* p = new string("Hello");
  9. data.store(42, memory_order_relaxed);
  10. ptr.store(p, memory_order_release);
  11. }

  12. void Consumer(){
  13. string* p2;
  14. while(!(p2 = ptr.load(memory_order_consume)));
  15. assert(*p2 == "Hello");
  16. assert(data.load(memory_order_relaxed) == 42);//可能失败,因为只能保证原子操作发生在与ptr有关的原子操作之前,相比于前面的例子,先于发生关系弱化了
  17. }
  18. int main()
  19. {
  20. thread t1(Producer);
  21. thread t2(Consumer);
  22. t1.join();
  23. t2.join();
  24. return 0;
  25. }
复制代码

15.noexcept:如果noexcept抛出一场,编译器可以直接调用std::terminate()函数终止程序运行,有效的阻止异常的传播与扩散。比异常机制throw()的效率高,因为异常机制会带来额外的开销,比如抛出异常,会导致函数栈被依次地的展开,并依帧调用在本帧中已构造的自动变量的析构函数等。
语法:
  1. void excpt_func() noexcept;
  2. void except_func() noexcept (常量表达式);//表达式转化成bool,为true函数不抛出异常,
  3.                                        //false抛出异常,不加参数默认为true
复制代码
回到最初始的几个问题:
  • lambda是仿函数的一个“甜点”;灵活的闭包;增加STL迭代器算法灵活性;注意 [&] [=]
  • auto拥有复杂表达式的复杂类型声明时简化代码,decltype可以推导表达式类型并且重用。 非指针或者引用时auto不能带走cv限定符,decltype可以带走cv限定符。 auto与decltype配合->追踪返回类型: auto(*)() -> int(*)()// 一个返回函数指针的函数,假设为函数a auto pf1() -> auto(*)() -> int(*)() //一个返回a函数指针的函数
  1. template<typename T1, typename T2>
  2. auto Sum(T1 & t1, T2 & t2) -> decltype(t1 + t2)
  3. {
  4. return t1 + t2;
  5. }
复制代码

3.移动语义 移动构造:避免额外拷贝 std:move:将左值转为右值,可以将对象的状态或者所有权转换给另一个对象,没有内存拷贝 std:forward:对象转发时保留了其属性左值/右值
4.C++11关于内存内存管理两种方式:引用计数(有循环引用的问题);跟踪算法(标记清除,标记整理,标记拷贝) shared_ptr 一个原始内存指针可以拷贝给多个shared_ptr,内部实现为引用计数,当引用计数归0,会被删除 unique_ptr 对象是一个内存对象原始指针唯一管理者,可通过move转换权限,对象析构的时候释放内存 weak_ptr 是shared_ptr的监控
5.default产生缺省版本,delete显示删除函数
6.final 派生类不可以重写该函数,override派生类重载基类的函数


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则