《Effective Modern C++》读书笔记暨C++11、C++14特性记录

版权声明:本文为博主原创文章,未经博主允许不得转载。文章中有连接失效或是技术谬误的地方,请与我联系。 https://blog.csdn.net/luchengtao11/article/details/83386491

C++新特性

1、Lambda表达式:https://www.cnblogs.com/DswCnblog/p/5629165.html

2、深入理解C++中的mutable关键字

3、volatile:C/C++中volatile关键字详解

4、c++中的左值与右值

5、std::thread

绪论

1、C++11标准中,std::auto_ptr就是个被废弃的特性,因为std::unique_ptr可以完成同样的任务,并且使用起来更方便。

参考:浅谈智能指针auto_ptr/shared_ptr/unique_ptr

第一章、型别推导

条款1:理解模版型别推导

template<typename T>
void f(ParamType param)

f(expr)

型别推导的过程就是通过expr来推导ParamType和T、分为三种情况

情况1:ParamType是一个指针或引用,但不是一个万能引用。

  • 若expr具有引用型别,先将引用部分忽略。
  • 而后,对expr的型别和ParamType的型别执行模式匹配,来决定T的型别。   

情况2:ParamType是一个万能引用

  • 如果expr是一个左值,T和ParamType都会被推导为左值引用。
  • 如果expr是一个右值,则应用“常规”(即情况1中的)规则

情况3:ParamType既非指针也非引用

  • 若expr具有引用型别,则忽略其引用部分。
  • 忽略expr的引用性后,若expr是个const对象,也忽略之。若其是一个volatile对象,也忽略之。

条款2:理解auto型别推导

 

1、auto型别推导和模板型别推导的运作是类似的。

2、注意

auto x1=27;
auto x2(27);
auto x3 = {27};
auto x4{27};
//这些声明都能通过编译,但结果却与一开始并不完全相同。前面两个语句确实仍然声明了一个型别为int值为27的变量。而后面两个语句,却声明了一个型别为std::initializer_list<int>且含有单个值为27的元素。

条款3:理解decltype

1、一般来说,含有型别T的对象的容器,其operator[]会返回T&。std::deque就属于这种情况,而std::vector也几乎总是属于这种情况。只有std::vector<bool>对应的operator[]并不返回bool&,而返回一个全新对象。

条款4:掌握查看型别推导结果的方法

1、编译器诊断信息。

2、运行时输出。

std::cout<<typeid(x).name()<<std::endl;//输入x的型别

第二章 auto

条款5:优先选用auto,而非显式型声明

1、可以向未初始化的变量带来的问题挥手告别

2、可以用它来表示只有编译器才能掌握的型别

3、显式指定型别可能导致你既不想要,也没想到的隐式型别转换。使用auto可以防止这个问题。

条款6:当auto推导的型别不符合要求时,使用带显式型别的初始化物习惯用法。

1、std::vector<bool>做过特化,用一种压缩形式表示特有的bool元素,每个bool元素用一个比特来表示。C++中禁止比特的引用。

第三章 转向现代C++

条款7:在创建对象时注意区分()和{}

1、不可复制的对象(如std::atomic型别的对象)可以采用大括号和小括号进行初始化,却不能用=

2、在C++的三种初始化表达式的写法中,只有大括号适用于所有场合。大括号可以阻止隐式窄化型别转换,还对最令人苦恼之解析语法免疫。

3、如果使用大括号初始化物来初始化一个使用auto声明的变量,那么推导出来的型别就会成为std::initializer_list。编译器只要有任何可能把一个采用大括号初始化语法的调用语句解读为带有std::initializer_list型别形参的构造函数,则编译器就会选用这种解释。只有在找不到任何办法把大括号初始化物中的实参转换为std::initializer_list模板中的型别时,编译器才会退而去检查普通的重载协议。

条款8:优先使用nullptr,而非0或NULL

1、相对于0和NULL,优先使用nullptr。避免在整型和指针型别之间重载。

条款9:优先选用别名声明,而非typedef

1、typedef不支持模板化,但别名声明支持

2、别名模板可以让人免写“::type”后缀,并且在模板内,对于内嵌typedef的引用经常要求加上typename前缀。

第七章 并发API

条款35 优先选用基于任务而非基于线程的程序设计

1、最高水平的线程调度器会使用全系统范围的线程池来避免超订,而且还会通过运用工作窃取算法来提高硬件内核间的负载均衡。

2、std::thread的API未提供直接获取异步运行函数返回值的途径,而且如果那些函数抛出异常,程序就会终止。

3、基于线程的程序设计要求手动管理线程耗尽、超订、负载均衡、以及新平台适配。

4、经由应用了默认启动策略的std::async进程基于任务的程序设计,大部分这类问题都能找到解决之道。

条款36 如果异步是必要的,则指定std::launch::async

1、std::async的默认启动策略既允许任务以异步方式执行,也允许任务以同步方式执行。

2、如此的弹性会导致使用thread_local变量时的不确定性,隐含着任务可能永远不会执行,还会影响运用了基于超时的wait调用的程序逻辑。

3、如果异步是必要的,则指定std::launch::async

条款37:使std::thread型别对象在所有路径皆不可联结

 

1、使std::thread型别对象在所有路径皆不可联结

2、在析构时调用join可能导致难以调试的性能异常

3、在析构时调用detach可能导致难以调试的未定义行为

4、在成员列表的最后声明std::thread型别对象

条款38:对变化多端的线程句柄析构函数行为保持关注

1、期值的析构函数在常规情况下,仅仅会析构期值的成员变量。

2、指涉经由std::aysnc启动为未推迟任务的共享状态的最后一个期值会保持阻塞,直至该任务结束。也就是std::future析构函数可能会阻塞。

条款39:考虑针对一次性事件通信使用以void为模板型别实参的期值

1、如果仅为实现平凡事件通信,基于条件变量的设计会要求多余的互斥量,这会给相互关联的检测和反应任务带来约束,并要求反应任务校验事件确已发生。

2、使用标志位的设计可以避免上述问题,但这一设计基于轮询而非阻塞。

3、条件变量和标志位可以一起使用,但这样的通信机制设计结果不甚自然。

4、使用std::promise型别对象和期值就可以回避这些问题,但是一来这个途径为了共享状态需要使用堆内存,而且仅限于一次性通信。

条款40:并发使用std::atomic,对特种内存使用volatile

1、atomic通常会使用特殊的机器指令来实现,这些指令比使用互斥量来得更加高效。

2、atomic型别不支持复制构造、复制赋值、移动构造、移动赋值。

3、volatile用于读写操作不可以被优化掉的内存。它是在面对特种内存时使用的工具。

 

 

 

展开阅读全文

没有更多推荐了,返回首页