[读书笔记]Effective C++ - Scott Meyers

[读书笔记]Effective C++ - Scott Meyers

条款01:视C++为一个语言联邦

C++四个次语言:
1. C
Part-of-C++,没有模板、异常、重载。
2. Object-Oriented C++
C-with-Classes,面向对象程序设计在C++上最直接的实施:类、封装、继承、多态、虚函数……
3. Template C++
C++泛式编程部分,出现模板(template)。
4. STL
STL是template程序库,实现各种容器、迭代器、算法。

条款02:尽量以const,enum,inline替换#define

#define不被视为语言的一部分,不被记录到符号表中。
无法利用#define来定义类专属常量,因为#define不重视作用域。
解决:单纯常量,使用const对象或者enums替换#define,形似函数的宏(macros),最好用inline函数替换#define

条款03:尽可能使用const

注意:两个成员函数如果只是常量性的不同,可以被重载。
优点:避免由于无意间修改数据导致的编程错误,而且可以使得函数能够处理const和非const实参,否则只能接受非const数据。如果条件允许,则应将指针形参声明为指向const的指针。
const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
当const和非const成员函数有着实质等价实现时,另非const版本调用const版本可避免代码重复。

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

1. 确保每个构造函数都将对象的每一个成员初始化。
2. 类构造函数使用member initalization list(成员初值列)替换赋值动作,不要在构造函数本体内使用赋值操作,可以避免默认构造函数进行初始化的开销。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
3. 为内置类型对象进行手工初始化,因为C++不保证初始化它们。
4. 为免除“跨编译单元之初始化次序问题”,以local static对象替换非local static对象(单例模式)。

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

必须显式声明复制赋值操作符:
1. 类内有引用成员。
2. 类内有const成员
3. 基类将复制赋值操作符声明为private,编译器将拒绝为派生类自动生成。

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

将复制构造函数和复制赋值操作符声明为private,并且只声明不给出定义,可以避免外部调用(编译器报错)和成员函数或友元函数调用(链接器报错)。
最佳解决方法,继承一个拥有private复制构造函数和复制赋值操作符的基类,这样会在编译期报错,更早发现错误。

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

基类带一个非virtual析构函数,会导致派生类析构时,只销毁基类成分,派生类成分没有被销毁,造成资源泄露。
注意:标准string、STL容器不含任何virtual函数,不要将其当做基类。
带多态性质的基类应该声明一个virtual析构函数,如果类带有任何virtual函数,也应该拥有一个virtual析构函数。

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

析构函数绝对不要吐出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

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

因为这类调用从不下降至下层派生类。

条款10:令operator= 返回一个 *this的引用

为实现连锁赋值,赋值操作符必须返回一个指向操作符左侧实参的引用。

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

证同测试:如果是自我赋值,就不做任何事。
copy-and-swap:先为实参制作一份副本,*this数据与上述副本交换。

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

如果自己实现复制构造函数,添加成员变量同时要修改复制构造函数。确保赋值对象内所有成员变量以及所有基类成分。

条款13:以对象管理资源

auto_ptr被销毁时自动删除所指之物,通过复制构造函数或者复制赋值操作符复制,它们会变成NULL,复制所得指针取得资源唯一所有权。
为防止资源泄露,使用RAII对象。

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

RAII:Resource Acquisition Is Initialization 资源取得设计便是初始化时机。
抑制copying,施行引用计数法(tr1:shared_ptr)

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

APIs往往要求访问原始资源,所以每一个RAII类应该提供一个“取得其所管理资源的方法”。
对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户更加方便。

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

调用new时,第一,内存会被分配出来,第二,针对此内存会有一个或者更多构造函数被调用。
调用delete,第一,针对此内存会有一个或更多析构函数被调用,第二,释放内存。
删除对象: delete object
删除由对象组成的数组:delete [] object

条款17:以独立语句将newed对象置入智能指针

以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。

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

促进正确使用的方法包括接口一致性,以及与内置类型的行为兼容。
阻止误用的方法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

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

类的设计就是type的设计。

条款20:宁以const引用传递替换值传递

值传递往往会引起至少一次的构造函数和析构函数的调用。而且值传递会使参数被切割,派生类对象特化信息被切除。
以上规则不适用于内置类型,以及STL的迭代器和函数对象。对象如果属于内置类型,值传递比引用传递效率高一些。

条款21:必须返回对象时,不要返回其引用

绝不要返回指针或引用指向一个局部栈对象。
返回引用指向堆分配对象。
或返回指针或引用指向局部static对象。

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

切记将成员变量声明为private,这可以赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
protected并不必public更具封装性。

条款23:宁以非成员函数,非友元函数替换成员函数

增加封装性、包裹弹性和技能扩充性。

条款24:若所有参数皆需类型转换,采用非成员函数

需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个非成员函数。

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

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

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

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

const_cast被用来将对象的常量性转除
dynamic_cast主要用来执行“安全向下转型”,是唯一无法由旧式语法执行的动作,是唯一无法由旧式语法执行的动作
reinterpret_cast执行低级转型,实际动作和结果取决于编译器。
static_cast用来强迫隐式转换
新式转型更受欢迎,因为容易在代码中被辨识出来,转型动作窄化,编译器更能诊断出错误的使用。
如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast
如果转型是必要的,将它隐藏在某个函数背后,客户随后可以调用该函数,不需要将转型放进自己的代码中。
宁可使用C++-style新式转型,也不要使用旧式转型,前者容易辨认。

条款28:避免返回handles指向对象内部成分

避免返回handles(包括引用、指针、迭代器)指向对象内部,以增加封装性,帮助const成员函数行为像个const

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

异常安全函数及时发生异常也不会泄露资源或允许任何数据结构损坏,这样的函数区分为三种可能的保证:基本型、强烈性、不抛异常型。

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

过度热衷inlineing会造成程序体积太大,代码膨胀会导致额外的换页行为,降低cache命中率,降低效率。

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

如果使用对象的引用或者对象的指针能够完成任务,就不要使用对象
尽量以类的声明式替换类的定义式
为声明式和定义式提供不同的头文件
支持编译依存性最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
程序库的头文件应该以完全企鹅仅有声明式的形式存在。

条款32:确定你的public继承塑模出is-a关系

适用于基类身上的每一件事情一定也适用于派生类身上..

条款33:避免hide继承而来的名称

使用using声明式或转交函数。

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

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

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

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

基类指针,指向派生类,这个指针的静态类型为父类,动态类型为目前所指的对象的类型。
如果缺省的参数值是动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的参数缺省值,这比目前实行的“在编译期决定”的机制更慢而且更复杂。

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

复合的意义和public继承完全不同。

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

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

非必要不使用virtual基类,平常使用非virtual继承,如果必须使用virtual继承,尽可能避免在其中放置数据。
多重继承比单一继承复杂,会导致新的歧义性以及对virtual继承的需要。
virtual继承会增加大小、速度、初始化及赋值复杂度等成本。

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

类和模板都支持接口和多态。
对类而言接口是显式的,以函数签名为中心,多态则是通过virtual函数发生于运行期。
对模板参数而言,接口是隐式的,基于有效表达式,多态则是通过模板具现化和函数重载解析发生于编译期。

条款42:了解typename双重意义

声明template参数时,前缀关键字typename和class等价。

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

可在派生类模板内通过 this-> 指涉基类模板内的成员名称,或籍由一个明白写出的基类资格修饰符完成。

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

templates生成多个类和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或类成员变量替换template参数。
因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。

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

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

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

条款48:认识模板元编程

TMP是编写template-based C++程序并执行于编译期的过程。
可以使工作由运行期迁移到编译期,因而得以实现早起的错误侦测和更高的执行效率。

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

set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
Nothrow new 是一个破位局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

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

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

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

operator new应该内含一个无穷循环,并且在其中尝试分配内存,如果它无法满足内存需求,就该调用new-handler,它也应该有能力处理0 byte申请。
operator delete应该在收到NULL指针的时候不做任何事。

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

条款53:不要护士编译器的警告

努力在编译器的最高警告级下争取“无任何警告”。
不要过度以来编译器的报警能力。

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

C++98主要成分:
1. STL,覆盖容器,迭代器,算法,函数对象,容器适配器,函数对象适配器
2. Iostreams,覆盖用户自定缓冲功能,国际化I/O,以及cin,cout,cerr,clog。
3. 国际化支持
4. 数值处理
5. 异常阶层体系
6. C89标准程序库
TR1新组件:
1. 智能指针
2. tr1::function
3. tr1::bind
4. Hash tables,用来实现sets,multisets,maps,multi-maps
5. 正则表达式,包括以正则表达式为基础的字符串查找和替换,或者从某个匹配字符串到另一个匹配字符串的逐一迭代。
6. Tuples
7. tr1::array,本质是STL化的数组
8. tr1::mem_fn
9. tr1::reference_wrapper,一个让引用的行为更像对象的设施。
10. 随机数生成工具
11. 数学特殊函数
12. C99兼容扩充
13. Type traits
14. tr1::result_of,这是一个template,用来推到函数调用的返回类型。

条款55:让自己熟悉Boost

Boost是一个C++开发者集结的社群,也是一个可自由下载的C++程序库群。提供了许多TR1组件的实现品,以及其他许多程序库。

猜你喜欢

转载自blog.csdn.net/zuimrs/article/details/79749422