《Effective C++》个人总结

标注书中一些个人容易遗忘的知识点。由于本人是在阅读完C++ Primer后再阅读Effective C++的,所以会补充一些从C++ Primer学到的C++11的特性(14、17暂不补充)

条款1:视 C++ 为一个语言联邦(C、Object-Oriented C++、Template C++、STL)

条款2:宁可以编译器替换预处理器(尽量以 constenuminline 替换 #define

对于单纯常量,最好以const对象或enums替换#defines

对于形似函数的宏(macros),最好改用inline函数替换#defines

条款3:尽可能使用 const

条款4:确定对象被使用前已先被初始化

构造时赋值(copy 构造函数)比 default 构造后赋值(copy assignment)效率高

为内置型对象进行手工初始化,因为C++不保证初始化它们。

构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明序列相同。

为免除“跨编译单元之初始化序列”问题,请以local static对象替换non-local static对象。

条款5:了解 C++ 默默编写并调用哪些函数

编译器暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符、析构函数

条款6:若不想使用编译器自动生成的函数,就应该明确拒绝

将不想使用的成员函数声明为 private,并且不予实现

在新标准下,我们可以通过将拷贝构造函数和靠背赋值运算符定义为删除的函数(deleted function)来阻止拷贝。删除的函数是这样的一种函数:我们虽然声明了它们,但不能以任何方式使用它们。

条款7:为多态基类声明 virtual 析构函数

如果带有多态性的base class 带有任何 virtual 函数,它就应该拥有一个 virtual 析构函数

有时我们会定义这样一种类,我们不希望其他类继承它,或者不考虑它是否适合作为一个基类。在新标准下,,在类名后跟一个关键字final

条款8:别让异常逃离析构函数

析构函数应该吞下不传播异常,或者结束程序,而不是吐出异常;

如果要处理异常应该在非析构的普通函数处理

条款9:绝不在构造和析构过程中调用 virtual 函数

因为这类调用从不下降至 derived class

条款10:令 operator= 返回一个 reference to *this (用于连锁赋值)

令赋值操作符返回一个reference to*this

条款11:在 operator= 中处理 “自我赋值”

确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap

确定任何函数如果操作一个以上的对象,而其中多个对象时同一对象时,其行为仍然正确

条款12:复制对象时勿忘其每一个成分

赋值对象时应确保复制 “对象内的所有成员变量” 及 “所有 base class 成分”(调用基类复制构造函数)

不要尝试以某个拷贝函数实现另一个拷贝函数。应该将共同机能放进第三个函数中,并由两个拷贝函数共同调用

条款13:以对象管理资源

资源在构造函数获得,在析构函数释放,建议使用智能指针,资源取得时机便是初始化时机(Resource Acquisition Is Initialization,RAII)

为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。

两个常被使用的RAII Classes分别是shared_ptr和unique_ptr。

条款14:在资源管理类中小心 copying 行为

普遍的 RAII class copying 行为是:抑制 copying、引用计数、深度拷贝、转移底部资源拥有权(类似 unique_ptr)

当RAII对象被复制,可能出现如下四种情况:1、禁止复制;2、对底层资源祭出“引用计数法”;3、复制底部资源;4、转移底部资源的所有权

条款15:在资源管理类中提供对原始资源(raw resources)的访问

对原始资源的访问可能经过显式转换或隐式转换,一般而言显示转换比较安全,隐式转换对客户比较方便

APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理之资源”的方法

条款16:成对使用 new 和 delete 时要采取相同形式

new 中使用 [] 则 delete []new 中不使用 [] 则 delete

条款17:以独立语句将 newed 对象存储于(置入)智能指针(如果不这样做,可能会因为编译器优化,导致难以察觉的资源泄漏)

用形似

std::shared_ptr<Widget> pw(new Widget);
processWight(pw, priority());

取代

processWight(std::shared_ptr<Widget> pw(new Widget), priority());

条款18:让接口容易被正确使用,不易被误用

促进正常使用的办法:接口的一致性、内置类型的行为兼容;

阻止误用的办法:建立新类型,限制类型上的操作,约束对象值、消除客户的资源管理责任

由于shared_ptr对于实现这一功能非常有效,能防范DLL问题、可自动解除互斥锁。所以虽然比原始指针大且慢,而且使用辅助动态内存,但依然应该尽量使用

 如果接口要求客户必须记得做某些事情,就是有着“不确定使用”的倾向,就先发制人消除客户忘记使用的可能性

条款19:设计 class 犹如设计 type

需要考虑以下问题:

新type对象应该如何被创建和销毁;

初始化和赋值间的差别;

值传递意味着什么;

什么是新type的“合法值”(合法值的范围);

如果存在继承关系应如何配合;

新type需要实现什么样的转换;

确定什么样的操作符或函数是成员函数;

哪些标准函数应该被delete显式的禁用;

谁该使用type的成员;

什么是新type的“未声明接口”;

你的新type有多么一般化;

你真的需要一个新type吗。

条款20:宁以 pass-by-reference-to-const 替换 pass-by-value

前者通常更高效、避免切割问题(slicing problem),但不适用于内置类型、STL迭代器、函数对象

条款21:必须返回对象时,别妄想返回其 reference

绝不返回 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 eference 指向一个 local static 对象而有可能同时需要多个这样的对象。

条款22:将成员变量声明为 private

为了客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证、并提供class作者以充分的弹性(封装)

protected并不比public更具封装性

条款23:宁以 non-member、non-friend 替换 member 函数

可增加封装性、包裹弹性(packaging flexibility)、机能扩充性

比较自然的做法是将non-member、non-friend函数放入和调用类所在同一个命名空间内

条款24:若所有参数(包括被this指针所指的那个隐喻参数)皆须要类型转换,请为此采用 non-member 函数

member函数的反面是non-member函数,不是friend函数

条款25:考虑写一个不抛异常的 swap 函数

当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常

如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对与classes(而非templates),也请特化std::swap

调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”

为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西

条款26:尽可能延后变量定义式的出现时间

可增加程序清晰度并改善程序效率

对于循环:
//方法A:定义于循环外
Widget w;
for(int i = 0; i < n; i++)
{
    w = 取决于某个i的值;
    //
}

//方法B:定义于循环内
for(int i = 0; i < n; i++)
{
    Widget w(取决于i的某个值);
    //
}

做法A:1 个构造 + 1 个析构 + n 个赋值
做法B:n 个构造 + n 个析构如果 1.赋值成本比 “构造 + 析构” 成本低,2.正在处理效率高度敏感的部分,否则应该使用 B;B 中 w 作用域比 A 中更小,更有利于程序的可理解性和易维护性。

条款27:尽量少做转型动作

旧式:(T)expressionT(expression);新式:const_cast<T>(expression)dynamic_cast<T>(expression)reinterpret_cast<T>(expression)static_cast<T>(expression)、;

尽量避免转型、注重效率避免 dynamic_casts、尽量设计成无需转型、可把转型封装成函数、宁可用新式转型

条款28:避免使用 handles(包括 引用、指针、迭代器)指向对象内部

以增加封装性、使 const 成员函数的行为更像 const、降低 “虚吊号码牌”(dangling handles,如悬空指针等)的可能性,但这决不意味着不可以让成员函数返回handle

条款29:为 “异常安全” 而努力是值得的

异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏,分为三种可能的保证:基本承诺、强列承诺、不抛掷保证

“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义

函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

条款30:透彻了解 inlining 的里里外外

inlining 在大多数 C++ 程序中是编译期的行为

inline 函数是否真正 inline,取决于编译器

大部分编译器拒绝太过复杂(如带有循环或递归)的函数 inlining,而所有对 virtual 函数的调用(除非是最平淡无奇的)也都会使 inlining 落空

inline 造成的代码膨胀可能带来效率损失;inline 函数无法随着程序库的升级而升级

将大多数inline限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化

不要只因为function templates出现在头文件中,就将它们声明为inline

有时候虽然编译器有意愿inlining某个函数,但还是可能为该函数生成一个函数本体(函数指针)

构造函数和析构函数往往是inlining的糟糕候选人

条款31:将文件间的编译依存关系降至最低

如果使用 object references 或 object pointers 可以完成任务,就不要使用 objects;如果能够,尽量以 class 声明式替换 class 定义式;为声明式和定义式提供不同的头文件

如果难以理解可以看下面这个回答:如何理解《Effective C++》第31条将文件间的编译依赖关系降低的方法? - 罗志宇的回答 - 知乎 https://www.zhihu.com/question/52832178/answer/192499529

条款32:确定你的 public 继承塑模出 is-a(是一种)关系

适用于 base classes 身上的每一件事情一定适用于 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象(正如每个学生都是人,但并非每个人都是学生)

条款33:避免遮掩继承而来的名字

derived classes内的名称会遮掩base classes内的名称。在public 继承从来没有人希望如此

可使用 using 声明式或inline 转交函数(forwarding functions)来让被遮掩的名字再见天日

条款34:区分接口继承和实现继承

在 public 继承之下,derived classes 总是继承 base class 的接口

  pure virtual 函数只具体指定接口继承

  非纯 impure virtual 函数具体指定接口继承及缺省实现继承

  non-virtual 函数具体指定接口继承以及强制性实现继承

条款35:考虑 virtual 函数以外的其他选择

Template Method 设计模式的一种特殊形式—— non-virtual interface(NVI)手法:以public non-virtual成员函数包裹较低访问性的virtual函数

将 virtual 函数替换为 “函数指针成员变量”是strategy设计模式的一种分解表现形式;

或者以 tr1::function 成员变量替换 virtual 函数,因而允许使用任何可调用物搭配一个兼容于需求的签名式,也是strategy设计模式的一种形式;

将继承体系内的 virtual 函数替换为另一个继承体系内的 virtual 函数,是strategy设计模式的传统实现手段(将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员)

条款36:绝不重新定义继承而来的 non-virtual 函数

条款37:绝不重新定义继承而来的缺省参数值

因为缺省参数值是静态绑定(statically bound),而 virtual 函数却是动态绑定(dynamically bound)

你可以使用条款35提供的替代方法,如NVI手法

class Shape{
public:
    enum ShapeColor{Red,Green,Blue};
    void draw(ShapeColor color=Red)const{
        doDraw(color);
    }
private:
    virtual void doDraw(ShapeColor color)const=0;
};

class Rectangle:public Shape{
public:
    ...
private:
    virtual void doDraw(ShapeColor color)const;
    ...
}

条款38:通过复合塑模 has-a(有一个)或 “根据某物实现出”

在应用域(application domain),复合意味 has-a(有一个)

在实现域(implementation domain),复合意味着 is-implemented-in-terms-of(根据某物实现出)

条款39:明智而审慎地使用 private 继承

private 继承意味着 is-implemented-in-terms-of(根据某物实现出),尽可能使用复合,虽然这样可能有较大的复杂度

当 derived class 需要访问 protected base class 的成员,或需要重新定义继承一或多个 virtual 函数,或需要 empty base optimization(空白基类最优化)时,才使用 private 继承

empty base:从未拥有non-static成员变量,但可拥有typedefs,enums,static成员变量,或non-virtual函数

条款40:明智而审慎地使用多重继承

多继承比单一继承复杂,可能导致新的歧义性,以及对 virtual 继承的需要,但确有正当用途,如 “public 继承某个 interface class” 和 “private 继承某个协助实现的 class”;virtual 继承可解决多继承下菱形继承的二义性问题,但会增加大小、速度、初始化及赋值的复杂度等等成本

条款41:了解隐式接口和编译期多态

class 和 templates 都支持接口(interfaces)和多态(polymorphism)

class 的接口是以函数签名(函数名称、参数类型、返回类型)为中心的,是显式的(explicit),多态则是通过 virtual 函数发生于运行期

template 的接口是隐式的(implicit),奠基于有效表达式的,多态则是通过 template 具现化和函数重载解析(function overloading resolution)发生于编译期

条款42:了解 typename 的双重意义

声明 template 类型参数是,前缀关键字 class 和 typename 的意义完全相同

请使用关键字 typename 标识嵌套从属类型名称,但不得在基类列(base class lists)或成员初值列(member initialization list)内以它作为 base class 修饰符

条款43:学习处理模板化基类内的名称

编译器不进入base class作用域查找可在 derived class templates 内通过 this-> 指涉 base class templates 内的成员名称,或藉由一个明白写出的 “base class 资格修饰符” 完成

条款44:将与参数无关的代码抽离 templates

Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系

因类型模板参数(non-type template parameters)而造成代码膨胀往往可以通过函数参数或 class 成员变量替换 template 参数来消除

因类型参数(type parameters)而造成的代码膨胀往往可以通过让带有完全相同二进制表述(binary representations)的实现类型(instantiation types)共享实现码

条款45:运用成员函数模板接受所有兼容类型

请使用成员函数模板(member function templates)生成 “可接受所有兼容类型” 的函数

声明 member templates 用于 “泛化 copy 构造” 或 “泛化 assignment 操作” 时还需要声明正常的 copy 构造函数和 copy assignment 操作符

条款46:需要类型转换时请为模板定义非成员函数

当我们编写一个 class template,而它所提供之 “与此 template 相关的” 函数支持 “所有参数之隐式类型转换” 时,请将那些函数定义为 “class template 内部的 friend 函数”

条款47:请使用 traits classes 表现类型信息

traits classes 通过 templates 和 “templates 特化” 使得 “类型相关信息” 在编译期可用,通过重载技术(overloading)实现在编译期对类型执行 if...else 测试

条款48:认识 template 元编程

模板元编程(TMP,template metaprogramming)可将工作由运行期移往编译期,因此得以实现早期错误侦测和更高的执行效率

TMP 可被用来生成 “给予政策选择组合”(based on combinations of policy choices)的客户定制代码,也可用来避免生成对某些特殊类型并不适合的代码

条款49:了解 new-handler 的行为

new-handler必须做:让更多内存可被使用,安装另一个new-handler,卸除new-handler,抛出bad-alloc,不返回

set_new_handler 允许客户指定一个在内存分配无法获得满足时被调用的函数

nothrow new 是一个颇具局限的工具,因为它只适用于内存分配(operator new),后继的构造函数调用还是可能抛出异常

template<typename T>
class NewHanlderSupport {
public:
    static std::new_handler set_new_handler(std::new_handler h) throw();
    static void* operator new(std::size_t size)throw(std::bad_alloc);
private:
    static std::new_handler currenthandler;
};

template<typename T>
std::new_handler NewHanlderSupport<T>::set_new_handler(std::new_handler p) throw()
{
    std::new_handler oldhandler = currenthandler;
    currenthandler = p;
    return oldhandler;
};

template<typename T>
void* NewHanlderSupport<T>::operator new (std::size_t size) {
    NewHandlerSupport<T> h(std::set_new_handler(currenthandler));
    return operator new(size);
}
template<typename T>
std::new_handler NewHanlderSupport<T>::currenthandler = nullptr;

条款50:了解 new 和 delete 的合理替换时机

为了检测运用错误、收集动态分配内存之使用统计信息、增加分配和归还速度、降低缺省内存管理器带来的空间额外开销、弥补缺省分配器中的非最佳齐位、将相关对象成簇集中、获得非传统的行为

条款51:编写 new 和 delete 时需固守常规

operator new 应该内涵一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就应该调用 new-handler,它也应该有能力处理 0 bytes 申请,class 专属版本则还应该处理 “比正确大小更大的(错误)申请”;operator delete 应该在收到 null 指针时不做任何事,class 专属版本则还应该处理 “比正确大小更大的(错误)申请”

条款52:写了 placement new 也要写 placement delete

当你写一个 placement operator new,请确定也写出了对应的 placement operator delete,否则可能会发生隐微而时断时续的内存泄漏

当你声明 placement new 和 placement delete,请确定不要无意识(非故意)地遮掩了它们地正常版本(正确做法:建立一个base class,内含所有正常形式的new和delete)

条款53:不要轻忽编译器的警告

严肃对待编译器发出的警告信息。努力在编译器的最高警告级别下争取“无任何警告”的荣誉

不要过度依赖编译器的报警能力,因为不同编译器对待事情的态度可能不同。一旦移植到另一个编译器,你原本依赖的警告信息有可能消失

条款54:让自己熟悉包括 TR1 在内的标准程序库

TR1,C++ Technical Report 1,C++11 标准的草稿文件

条款55:让自己熟悉 Boost(准标准库)

原文地址:https://www.cnblogs.com/GodZhuan/p/14152118.html