More Effective C++笔记
笔记,不值得看。以前记在印象笔记的,搬迁出来做个记录
说实话这个已经过时了。c++最近几年来发展的太快。对应的书已经不值得买了
term 2 const enum inline #define
用编译器替换预处理器
- const char * const authorName = “Scott Meyers”;
- static const statement OR enum hack(old compiler)
- inline func same efficiency with macro
term 3 Use const whenever possible
constant cant assign -> avoid assign error
term 4 成员初始列表 copy construct instead of default assignment construction
以及初始化顺序,以及non-local static变量初始化顺序(多文件分布,有依赖性)
函数wrapper singleton
多线程中可能会有问题
Constructors, Destructors, and Assignment Operators
term 5 明白c++在背后做了什么
term 6 不想用编译器自动生成的函数,就应该明确拒绝
声明为private 不实现 且不实现 (可能在链接期出错)-> noncopyable 基类继承
term 7 多态基类声明虚构函数
(多态)基类必有虚析构,无虚析构不基类,有虚函数必有虚析构
纯虚析构 无法实例化
term 8 异常与析构
term 9 构造函数与析构函数中不能有虚函数
虚函数不会是运行时继承的版本,而是基类的版本。构造期间,虚函数不是真正的虚函数 加中间层也不行
term 10 令 operator= 返回一个ref to *this
作为右值
A& operator= (const A& rhs)
{
return *this
}
term 11 operator= 处理自我复制,证同测试
但是不具备异常安全性,这种事情看个人处理。也可以不处理自我复制,保证异常安全就可以了
OR copy and SWAP
term 12 复制对象复制完整
尤其注意派生类可能出现的缺省构造行为,你的复制并不完整,你需要自己手写拷贝构造,或者调用基类的拷贝构造 //:这条需要重新揣摩
-—————————–
如果用默认的赋值操作,就是逐个成员赋值,对于指针对象赋值来说,至少两个问题
1内存泄露,其中有一个指针所指的对象丢失。2 析构一个指针导致另外一个指针成为悬挂指针
传值调用(形参赋值)造成的以上两种行为 正是auto_ptr效果。一个猜测,auto_ptr这么搞的
只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 m29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。
Resources Management
term 13 以对象管理资源
RAII auto_ptr shared_ptr
term 14 资源管理类中小心copy行为
自己约束是引用计数还是禁止拷贝
term 15 资源管理类中提供对原始资源的访问
显式安全,隐式友好。
term 16 couplle operator new and delete
term 17 存newed对象在shared_ptr,另起一行。//:没用到过
Designs and Declarations
term 18 接口友好不易误用
term 19 设计class犹如设计type
- 如何被创建与销毁
- 初始化与赋值差别
- 传值?
- 合法值?
- 继承?
- 转换?
- 操作符?
- 不应该实现的成员函数
- 成员权限
- 为生命接口
- 一般化?
- 你是否真的需要一个新type
term 20 pass-by-reference-to-const to pass-by-value 不包括 POD, iterator, functor.
也可以避免对象切割问题 //:需要例子
term 21 必须返回对象时不能返回引用
任何时候看到一个引用你都得确定他的真身 局部变量
term 22 成员变量 private 封装
term 23 成员函数封装成员函数 很糟糕,应该用普通函数来封装成员函数 wrapper
term 24 如果参数需要类型转换 ,运算符重载放在外边,不写成成员函数 视条件而定
term 25 swap swap成员函数 wrapper xxx::swap
implementations
term 26 用到在定义 话说回来,因为定义顺序而导致bug应该是十分愚蠢的bug了。全局变量也是资源。
term 27 尽量少做转型 cast
term 28 避免返回handles指向对象内部成员
term 29 异常安全
term 30 inline
term 31 文件依赖
OOP
term 32 is-a 如果没有这种关系,就要重新考虑继承设计,尽可能把错误接口在编译期捕捉到
term 33 避免遮掩继承来的名称。这种问题就像局部变量掩盖全局变量一样。派生类成员函数掩盖基类函数 引入作用域来解决。不过有点脏就是了
term 34 接口继承与实现继承
纯虚函数是接口,其他形式的继承既有接口也有实现,选择性重写 OR 强制继承者提供
非虚函数 ->强制默认实现,必须接受 不变性和特异性的重要性界定取决于基类定义者。
term 35 NVI template method 设计模式
虚函数作为缺省行为 也可以由用户实现, 非虚成员函数做一个wrapper
term 36 派生类不重新实现非虚函数 会导致覆盖
term 37 派生类不重新实现缺省参数
term 38 has-a && is-implement-in-terms-of
复用 && 一个用list实现set的例子
term 39 use private inheritance judiciously 明智而审慎的使用private继承
term 40 MI 菱形缺陷 审慎使用
Templates and Generic Programming
term 41 编译期多态
term 42 typename
term 43 模板与基类
term 44 template 膨胀
term 45 成员函数模板接收所有兼容类型 //?
term 46 类型转换放在模板类外面。不要写成成员函数 //?
term 47 traits classes 类型萃取 编译期条件选择实现->偏特化模板 OR 函数重载 分支判定
category tag typedef typename IT::iterator_category iterator_category
term 48 tmp
Customizing new and delete
term 49 new-handler
term 50 替换new delete?
term 51 rewrite new delete
term 52 placement new delete both
Miscellany
term 53 warning
term 54 熟悉标准库 越熟越好
term 55 boost
Effective modern c++
item 1 理解模板类型推断
template
void f(ParamType param);
auto的规则与模板类型推断有很大关联,这在一开始已经说了,是理解auto的前提。
要记住的4点
- 在模板类型推断过程中,参数的引用语义会被忽略
- 在通用引用(universal reference)类型推断时,左值引用会被特别对待
- 在传值类型的模板推断过程中,const 和 volatile的参数会以non-const 和 non-volatile 对待。
- 在模板类型推断过程中,如果参数是数组或者函数,他们被转化为对应的指针,除非模板参数类型一开始就是引用。
item 2 理解auto类型推断
- auto类型推断与模板类型推断基本一致,不过auto会假定大括号是一个std::initializer_list,但是模板类型推断则不会。
- 当一个函数返回类型是auto或者在lambda的参数中使用auto时,它们的类型推断规则是用模板类型推断的规则,而不是auto类型推断。
item 3 理解decltype
- decltype几乎总是不加修改的返回名字或者表达式的类型。
- 一个类型为T左值表达式,除了名字外,decltype总是返回T&。
- C++14支持decltype(auto),行为是用decltype的规则来进行类型推断。
item 4 懂得如何查看已推断类型
这种查看已推断类型的方法对我们来说很美好,但是我们需要记住IDE编辑器、编译器报错信息和类似Boost.TypeIndex的库都仅仅是一个帮你找出编译器推断的类型结果的工具。虽说这些工具都对我们有帮助,但是,这所有的工具终究不能取代条款1~3里面的类型推断本质的理解。
需要记住的2点:
- 通常可以使用IDE编辑器,编译器报错信息和Boost TypeIndex库来查看已推断的类型。
- 一些工具的结果可能没有帮助或者不准确,所以理解透彻C++类型推断规则才是知道正确已推断类型的本质。
item 5 尽量用auto代替显示类型声明
事实上,显式声明对象很多时候会造成微妙的错误,不正确且没效率。而且,如果改变了初始表达式的类型,auto类型也会自动地改变,这意味着对重构(refectoring)有很大帮助。例如你写了个返回类型为int的函数,后来觉得long类型更好,那么你修改了函数后,下一次编译后用auto接收函数返回值的变量会自动更新类型。如果你用int接收,则需要修改每一处函数调用的代码。
需要记住的2点:
- auto变量必须初始化,它通常不会类型不匹配,从而刚轻便和更高效,还能减少重构的工作量,一般我们尽量用auto代替显式类型声明。
- auto类型变量会有条款2和条款6中的陷阱
item 6 当auto会推断出不合理的类型时使用显式类型初始化语法
- 不可见的代理类初始表达式可能会使auto推断出错误的结果
- 显示类型初始化语法迫使auto推断你想要的类型
item 7 创建对象分 ( ) { }
- 大括号初始化是适用范围最广泛的初始化语法,它可以防止范围窄化转换和免疫C++的most vexing parse问题。
- 在选择重载构造函数期间,大括号初始化会尽可能的匹配带std::initializer_list构造函数,尽管看起来匹配其它的构造函数刚好。
- 使用大括号和圆括号初始化导致巨大差异的一个例子是用两个参数创建std::vector
对象。 - 在模板中选择用大括号还是圆括号创建对象是具有挑战性的。
item 8 nullptr replace 0 & NULL
- 使用nullptr代替0和NULL。
- 避免整数类型与指针类型之间的函数重载。
item 9 用别名声明alias delcaration 代替 typedef
- typedef不支持模板化,但别名声明(alias declaration)支持。
- 别名声明(alias declaration)可以避免“::type”后缀,而且在模板中,typedef通常需求typename前缀。
- C++14 offers alias templates for all the C++11 type traits transformations.
item 10 比起unscoped enums更偏爱scoped enums
- C++98风格的enum现在被称为unscoped enum。
- scoped enum的枚举值只在enum内可见,它们只能通过cast来转换成其他类型。
- scoped enum和unscoped enum都支持指定基础类型(underlying type),scoped enum的默认基础类型是int,unscoped enum没有默认基础类型。
- scoped enum总是可以前向声明,unscoped enum只有在它声明时指定了基础类型才可以前向声明。
item 11 用deleted functions代替private undefined的做法 c++11
- 用deleted functions代替private undefine的做法。
- 任何函数都有可能被删除,包括非成员函数和模板的实例化。
item 12 把重写函数(overriding function)声明为override
- 把重新函数声明为override(Declare overring functions override)。
- 成员函数引用资格(reference qualifiers)使区别对待左值和右值对象(*this)成为可能。
item 13 const_iterator
- 比起iterator更偏爱const_iterator。
- 在最大限度通用代码中,比起成员函数,更偏向使用非成员版本的begin,end,rbegin等。
item 14 把不发出异常的函数声明为noexcept
- noexcept是函数接口的一部分,这意味着调用者可能会依赖它。
- noexcept函数会比非noexcept函数优化得更多。
- noexcept对于移动赋值操作、swap、内存释放函数和析构函数具有重大的价值。
- 大部分函数是exception-neutral函数,而不是noexcept函数。
item 15 constexpr
- constexpr对象是const的,它需用编译期间已知的值初始化。
- constexpr函数在传入编译期已知值作为参数时,会在编译期间生成结果。
- constexpr对象和函数比起non-constexpr对象和函数具有更广泛的语境。
- constexpr是对象和函数接口的一部分。
item 16 const线程安全
- 让const成员函数做到线程安全,除非你能肯定它们决不在并发语境中使用。
- 使用std::atomic变量可能比互斥锁提供更好的性能,不过它们只适用于单一变量和单一存储单元。
item 17 理解特殊成员函数的生成
- 特殊成员函数是编译器可自动生成的函数:默认构造函数,析构函数,拷贝操作,移动操作。
- 移动操作只有在那些没有显式声明移动操作、拷贝操作、析构函数的类中生成。
- 拷贝构造只有在那些没有显式声明拷贝构造的类中生成,如果类中声明了移动操作它就会被删除。拷贝复制操作符只有在那些没有显式声明拷贝操作运算符的类中生成,如果类中声明了移动操作它会被删除。在显式声明析构函数的类中生成拷贝操作是被反对的。
- 成员函数模板从来不会抑制特殊成员函数的生成。
item 18 用std::unique_ptr管理独占所有权的资源
- std::unique_ptr是一个智能,快速,只可移动的智能指针,它以独占所有权语义管理资源。
- 默认情况下,通过delete来销毁资源,但可以指定自定义删除器。有状态的删除器和函数指针作为std::unique_ptr的删除器会增加std::unique_ptr对象的大小。
- 将std::unique_ptr转换为std::shared_ptr是容易的。
item 19 用std::shared_ptr管理共享所有权的资源
- std::shared_ptr提供了一种方便的垃圾回收方法,针对于任意的共享生命期的资源。
- 与std::unique_ptr相比,std::shared_ptr对象通常是它的两倍大,需要控制块和原子操作的引用计数。
- 默认销毁资源的方式是delete,但支持自定义删除器。删除器的类型不影响std::shared_ptr的类型。
- 避免用原生指针变量来创建std::shared_ptr。
item 20 把std::weak_ptr当作类似std::shared_ptr的、可空悬的指针使用
- 把std::weak_ptr当作类似std::shared_ptr的、可空悬的指针使用。
- std::weak_ptr的潜在用途包含缓存,观察者链表,防止std::shared_ptr循环。
item 21 比起直接使用new,更偏爱使用std::make_unique和std::make_shared
- 相比于直接使用new,make函数可以消除代码重复,提高异常安全,而且std::make_shared和std::allocate_shared生成的代码更小更快。
- 不适合使用make函数的场合包括需要指定自定义删除器和想要传递大括号初始值。
- 对于std::shared_ptr,使用make函数可能是不明智的额外场合包括(1)自定义内存管理函数的类和(2)内存紧张的系统中,有非常大的对象,然后std::weak_ptr比std::shared_ptr长寿。
item 22 当使用Pimpl Idiom时,在实现文件中定义特殊成员函数
- Pimpl Idiom通过减少类用户和类实现之间的编译依赖来减少编译时间。
- 对于类型为std::unique_ptr的pImpl指针,在头文件中声明特殊成员函数,但在实现文件中实现它们。尽管编译器默认生成的函数实现可以满足需求,我们也要这样做。
- 上一条的建议适用于std::unique_ptr,不适用于std::shared_ptr。
item 23 理解std::move和std::forward
- std::move表现为无条件的右值转换,就其本身而已,它不会移动任何东西。
- std::forward仅当参数被右值绑定时,才会把参数转换为右值。
- std::move和std::forward在运行时不做任何事情。
item 24 区分通用引用与右值引用
- 如果一个模板函数的参数类型是T&&而T是要被推断类型,或者一个对象使用auto&&声明,那么这个参数或者对象是个通用引用。
- 如果类型推断的类型不是精确的type&&,或者不发生类型推断,那么typ&&指代的是右值引用。
- 如果用右值初始化通用引用,通用引用相当于右值引用;如果用左值初始化通用引用,通用引用相当于左值引用。
item 25 对右值引用使用std::move 对通用引用使用std::forward
- 在最后一次使用右值引用或者通用引用时,对右值引用使用std::move,对通用引用使用std::forward。
- 在一个通过值返回的函数中,如果返回的是右值引用或通用引用,那么对它们做同样的事情(对右值引用使用std::move,对通用引用使用std::forward)。
- 如果局部变量有资格进行返回值优化(RVO),不要对它们使用std::move或std::forward。
item 26 避免对通用引用进行重载
- 对通用引用进行重载几乎总是会导致这个重载函数频繁被调用,超出预期。
- 完美转发构造函数是特别有问题的,因为在接受非const左值作为参数时,它们通常比拷贝构造匹配度高,然后它们还能劫持派生类调用的基类的拷贝和移动构造。
item 27 考虑26的替代方案
- 替代结合通用引用和重载的方法包括使用有区别的函数名、以lvalue-reference-to-const形式传递参数、以值语义传递参数和使用tag dispatch。
- 借助std::enable_if限制模板容许一起使用通用引用和重载,不过它控制着编译器可以使用通用引用重载的条件。
- 通用引用参数一般具有性能优势,但它们通常有适用性劣势。
item 28 引用折叠
- 引用折叠会出现在4中上下文:模板实例化,auto类型生成,typedef和类型别名声明的创建和使用,decltype。
- 当编译器在一个引用折叠上下文中生成了对引用的引用时,结果会变成一个引用。如果原来的引用中有一个是左值引用,结果就是个左值引用。否则,结果是个右值引用。
- 通用引用是——出现在类型推断区分左值和右值和出现引用折叠的上下文中的——右值引用。
item 29 假设移动语义不存在
- 假设移动操作是不存在的、不廉价的、不能用的。
- 在知道类型或支持移动语义的代码中,不需要这个假设。
item 30 熟悉完美转发失败的情况
- 当模板类型推断失败或推断出错误的类型时,完美转发会失败。
- 导致完美转发失败的几种实参有:大括号初始值,0和NULL代表的空指针,只声明的static const成员变量,模板函数名字和重载函数名字,位域。
item 31 对于lambda表达式,避免使用默认捕捉
- 默认引用捕获会导致空悬引用。
- 默认值捕获对空悬指针(尤其是this)很敏感,而且它会误导地表明lambda是独立的。
item 32 初始化捕获闭包
- 使用C++14的初始化捕获来把对象移到到闭包。
- 在C++11,借助手写类或std::bind模仿初始化捕获。
item 33 对需要std::forward的auto&&参数使用decltype
- 对需要std::forward的auto&&参数使用decltype(Use decltype on auto&& parameters to std::forward them.
item 34 bind vs lambda
- 比起使用std::bind,lambda有更好的可读性,更强的表达能力,可能还有更高的效率。
- 在C++11,只有在实现移动捕获或者绑定函数调用操作符模板时,std::bind可能是有用的。
item 35 线程编程与任务编程(异步)
- std::thread的API没有提供直接获取异步运行函数返回值的方法,而且,如果这些函数抛出异常,程序会被终止。
- 基于线程编程需要手动地管理:线程耗尽、oversubscription、负载均衡、适配新平台。
- 借助默认发射策略的std::async,进行基于任务编程可以解决上面提到的大部分问题。
item 36 如果异步执行是必需的,指定std::launch::async策略
- std::async的默认发射策略既允许任务异步执行,又允许任务同步执行。
- 这个灵活性(上一点)导致了使用thread_local变量时的不确定性,它隐含着任务可能不会执行,它还影响了基于超时的wait调用的程序逻辑。
- 如果异步执行是必需的,指定std::launch::async发射策略。
item 37 让std::thread对象在所有路径都无法连接
- 在所有路径上,让std::thread变得不可连接。
- 在销毁时用join会导致难以调试的性能异常。
- 在销毁时用detach会导致难以调试的未定义行为。
- 在成员变量列表最后声明std::thread
item 38 意识到线程句柄的析构函数的不同行为
- future的析构函数通常只是销毁future的成员变量。
- 最后一个引用shared state(它是在借助std::aysnc创建了一个非推迟任务时产生)的future会阻塞到任务完成。