高质量C++开发实践(effective C++)

条款01
C++分为四个次语言,C,Object-oriented C++,Template ++,STL

对于内置类型pass-by-value 比pass-by-reference高效,用户自定义类型pass-by-reference-to-const更好

STL中迭代器和函数对象都是在C指针的基础上塑造出来的,所以旧式的C pass-by-value再次试用

条款02
对于单纯常量最好使用const和enums代替defines

对于形似函数的宏,最好使用inline代替defines

条款03
如果const出现在星号左边 const * p,表示被指物是常量,如果const出现在星号右边 * const p,表示指针自身是常量

const iterator 表示iterator是一个常量,const_iterator表示iterator指向一个常量。

两个成员函数如果只是constness(常量性)不同,可以被重载

physical constness 和 logical constness,physical constness,成员函数只有不改变对象任何成员时,才可以说是const;logical constness,一个成员函数可以修改对象中的某些bits,但只有在客户端侦测不出来的情况下,
通过设置变量的mutable,释放掉non-static成员变量的bitwise constness约束

将某些东西声明为const可以帮助编译器侦测出错误用法

当const和non-const的成员函数有着实际等价的实现时,用non-const调用用const函数,可以避免重复,不可以反过来调用

条款04
进入构造函数体之前,会调用成员变量的默认构造函数初始化变量,因此,如果在构造函数体内进行赋值,相当于是多先初始化,再赋值,所以调用成员初始化列是比较高效的做法,对于内置类型则无所谓。

成员变量是按照声明的顺序来进行初始化的,不会按照初始化列表中所写的顺序进行初始化

non-local static对象是指,定义在global,namespace中的对象(全局性质的), class和file中的static对象,寿命直到程序结束为止

local-static 对象,是指定义在函数中的static对象

问题,如果某个编译单元中的non-local static对象初始化时,使用了另外一个编译单元的non-local static对象,他用到的这个对象可能尚未被初始化。

解决方法,把每个non-local static对象放到自己专属的函数内,该对象在函数中被声明为static,该函数返回对象的reference。这种方法在多线程环境下会有麻烦,解决的办法是单线程启动阶段调用所有类似的函数。

避免在对象初始化之前使用他们,一,手工初始化内置类型non-member对象,二,使用成员初始化列表初始化所有成员,三,初始化顺序不确定时(non-local static),加强设计,使用local static 替换non-local static

条款05
编译器会暗自为class创建default构造函数,除非用户定义了自己的构造函数(有实参,无实参或者拷贝构造)
编译器会暗自为class创建拷贝构造,拷贝赋值和析构函数,除非用户自己定义了对应的函数

如果class中有成员是引用,或者const,或者基类的赋值操作符被声明为private,则编译器不会再生成拷贝赋值操作符,需要用户自己定义

条款06
为了拒绝编译器默认生成的copy构造和赋值操作符,可以将对应的函数声明为private(member和friend还是能够调用),并且不实现,或者从uncopyable的类继承。

条款07
带多态性质的base class应该声明一个virtual 析构函数, 如果class带有virtual函数,那么应该同时具有virtual 析构函数

class的设计如果不是为了作为base class使用,或者不是用作多台,那么不应该声明virtual 析构函数

条款08
析构函数不要抛出异常,如果被析构函数调用的函数会抛出异常,析构函数应该捕获他,不传播或者结束程序,多个对象析构时抛出异常,C++对多个异常不太好处理

如果客户需要对某个函数被调用时抛出的异常做出反应,那么class应该提供一个普通函数而非析构函数执行该操作

条款09
构造函数和析构函数中不能调用virtual函数,构造和析构时不会调用子类的函数

条款10
赋值操作符返回reference to *this

条款11
确保当对象自我赋值时operator=有良好行为,其中的技术包括,比较来源和目标地址,精心安排语句顺序,及copy-and-swap

一个函数如果操作多个对象,而其中多个对象是同一对象时,其行为仍正确

条款12
任何时候,编写派生类的copy构造函数时,都要小心的copy基类部分,应该调用基类的copy构造函数
别用copy构造函数调用copy复制操作符,也别用copy赋值操作符调用copy构造函数,给一个没有初始化的对象赋值和对一个已经初始化的对象再次初始化都是不正确的。将共同的机能放到第三个函数中。

条款13
auto_ptr简单封装了指针,在析构时会删除指针所指对象,禁止多个auto_ptr指向同一对象,auto_ptr的复制动作会把使他指向null
带引用计数的智能指针无法打破环状引用

tr1::shared_ptr可以解决上述问题,

auto_ptr和shared_ptr析构函数中只会调用delete,不会调用delete[],因此不应该把数组的指针传递给他们,boost::scoped_array和boost::shared_array支持

条款14

资源管理类的copy行为,常见的方法有,禁止copy行为,对底部资源进行引用技术,底部资源深度复制,转移底部资源的所有权

条款15

条款16

不要对数组typedef,这样定义的类型,申请时使用new, 释放时需要delete[],不对称,容易出错

条款17
以独立语句把newed对象放入智能指针中,不过不这样,一旦异常抛出可能会导致内存泄漏

条款18

cross-DLL problem 在一个DLL中new,在另外一个DLL中delete会导致运行时错误,使用tr1::shared_ptr可以解决该问题,因为它使用的删除器来自产生shared_ptr的DLL

促进正确使用的方法,接口一致性,以及与内置类型的行为兼容

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

条款19

新type的对象如何创建和释放,初始化和赋值的区别,拷贝构造的实现,新type的合法值检查,继承关系中父类的约束,有子类时要提供虚析构函数,是否需要隐式类型转换,提供显示类型转换的函数,成员函数public,private,protected划分,成员变量的划分,效率,异常,资源使用方面的考虑,能否抽象为模版类,如果定义派生类只是为已有类型添加机制,说不定定义non-member函数或者template可以达到相同目的。

条款20

pass-by-valid仅适用于传递内置类型,STL迭代器和函数对象,其他情况尽量使用pass-by-reference-to-const

条款21
不要返回pointer或者reference to local-stack对象

条款22
将变量声明为private,可以赋予客户访问数据的一致性(都通过函数访问),可以进行访问控制,可以允诺约束条件一直得以维护(只会通过函数来访问变量),提供class作者实现的弹性

条款23

拿non-member non-frined函数替换member函数,这样做可以增加封装性(可以访问成员变量的函数更少了),将一个类的所有便利函数放到多个头文件中但隶属于一个名字空间,可以减少编译的依赖性,也方便功能的扩展(新增头文件)

条款24
只有当参数放在参数列表中时,才能满足隐式类型转换的基本条件,函数的调用者不可以被转换,因此如果需要进行类型转换应该使用non-member函数,尽量通过public函数访问成员,而不是使non-member函数称为friend

条款25
C++不允许偏特化class templates,当想尝试给一个function tempates偏特化时,进行函数重载可以达到相同的目标

当std:swap函数对你的对象效率不高时,提供一个swap成员函数,这个函数不能抛出异常

提供成员函数的同时,也需要提供一个non-member swap函数,体用成员函数,当对象的类型不是template时,特化std:swap版本,当类是template时,无法偏特化std:swap函数,因此在自己的命名空间中定一个重载的函数(无法给std空间中加入任何东西)

使用swap时,使用using std::swap 然后调用swap函数,不能在前面加任何名字空间修饰,这样编译器会选择最合适的swap函数

条款26
延后定义变量,直到使用时

条款27
一个单一对象可能拥有一个以上的地址,以base指向和以dervied指向在某些场景下是不同的(所有多继承和有的单继承)

转型过后会产生临时变量,使用时应该慎重,转型之后的变量并不是原始的变量

避免使用dynamic_cast,两种方式,一种是直接使用dervied类的对象,另外一种是在base类中增加virtual函数

条款28
成员变量的封装性取决于返回该成员reference的成员函数的访问级别。

const成员函数返回reference,而后者指向的对象在本对象之外,而他的指针在本对象内,那么函数的调用者可以修改

避免返回成员变量的reference 指针和迭代器,可以增加封装性,并降低出现dangling handles的可能性

条款29

异常章节(略过了)

条款30
inline某个函数,编译器会优化他
inline只是对编译器的申请,不是强制指令
定义在类中成员函数,或者friend函数是隐式inline的
Template和inline无关,如果所有的具现化版本都是inlined,那么在template前面加上inline,否则不加
一个表面上看起来是inline是否真的inline取决于编译器,(循环,递归 ,virtual)
编译器通常不会对通过函数指针调用实施inline
构造函数和析构函数其实并没有看起来那样有很少的执行过程,因此是否作为inline需要视情况而定
程序库中的inline函数无法随着程序库的升级而升级,客户程序必须重新编译,如果是非inline的直接连接即可

调试器对inline函数束手无策,无法设立断点

条款31

尽量使用object reference和object pointer
声明一个函数,纵使他用到了某个类,也不需要该class的定义
程序库作者应该提供两个头文件,一个声明头文件,一个定义头文件,而不是手动写前置声明
export关键字只有部分编译器支持

handle class和 interface class

class A 
{
     AImpl* pimpl;
}

class interface{
}

使用handleclass和interface class可以在修改实现代码的时候,对用户代码的影响最小,但是由于增加了virtual或者新增指针,在使用时需要考虑平衡。

条款32
public继承主张能够施行与base的行为,都能够施行在dervied上

如果在派生类中对基类中的重载函数进行重载或者重写,会遮掩基类中其他的重载函数(在派生类中不可见,调用会出错),为了解决这一问题, 需要引入using,这个规则同时适应与virtual和non-virtual

或者直接显示的输入(基类类型::函数名)

using声明会使所有同名函数都可见

你可能感兴趣的:(程序设计)