Effective C++ 第二章
2. Constructors, Destructors, and Assignment Operators 构造/析构/赋值运算
05: Know what functions C++ silently writes and calls
了解C++默默编写并调用哪些函数
-
编译器可以暗自为class创建默认构造函数、复制构造函数、赋值运算操作符和析构函数
空类(empty class)不是真正的空类,当有需要被调用以上函数时,编译器会默认创建class Empty { }; // 相当于 class Empty { public: Empty() { ... } Empty(const Empty& rhs) { ... } ~Emtpy( ) { ... } Empty& operator=(const Empty& rhs) { ... } };
06: Explicitly disallow the use of compiler-generated functions you do not want
若不想使用编译器自动生成的函数,就该明确拒绝
-
希望设计的类不支持以下操作
HomeForSale h1; HomeForSale h2; HomeForSale h3(h1); // 不能通过编译 h2 = h1; // 也不能通过编译
-
将拷贝构造函数、赋值运算操作符声明为private,阻止了编译器默认创建public版本
并不绝对安全,无法阻止成员函数和友元函数调用 -
将成员函数声明为private且不实现,如C++ iostream的设计
class HomeForSale { public: ... private: HomeForSale(const HomeForSale&); // 只有声明,没有定义 HomeForSale& operator=(const HomeForSale&); }
在成员函数和友元函数中调用,能通过编译,但不能链接,连接器会报错
- 在编译阶段避免链接期错误,设计一个专门阻止拷贝动作的基类,HomeForSale继承基类
class Uncopyable { protected: Uncopyable() { } ~Uncopyable() { } private: Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); }; class HomeForSale : private Uncopyable { // 不再声明 拷贝构造函数和赋值运算操作符 ... };
这里使用了private继承,public继承是is a关系,两者使用注意事项详见32和39。
07: Declare destructors virtual in polymorphic base classes
为多态基类声明virtual析构函数
- 多态的基类应该声明一个virtual析构函数;如果类带有任何virtual函数,也应该拥有一个virtual析构函数
- 类的设计目的不是作为基类使用,也不是为了具备多态性,则不应该声明virtual析构函数
原因:
实现虚函数的对象,包含了虚函数表指针(virtual table pointer),vptr指向一个由函数指针构成的数组- 对象的体积会增加(32位计算机上为4字节,64位计算机上为8字节)
- 和其他语言(如C语言)不再具有移植性,除非额外实现
- 所有的STL容器带有non-virtual析构函数,如vector,list,set等,避免继承一个标准容器或其他带有non-virtual析构函数的类
- 纯虚函数不能被实例化
08: Prevent exceptions from leaving destructors
别让异常逃离析构函数
- 在析构函数中加入
try catch
,吞下异常 - 提供新的函数关闭连接供客户使用
09: Nevel call virtual functions during construction or destruction.
绝不在构造和析构过程中调用virtual函数
- 构造和析构函数不会调用到子类的虚函数
10: Have assignment operators return a reference to *this
令 operator= 返回一个 reference to *this
- 实现了连续复制的语义:
x = y = z = 1;
==>x = (y = (z = 1));
11: Handle assignment to self in operator=.
在operator= 中处理自我赋值
- 比较来源对象和目标对象的地址
Widget& Widget::operator=(const Widget& rhs) { if (this == &w) return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this; }
- 精心周到的语句顺序
Widget& Widget::operator=(const Widget& rhs) { Widget *pOrig = pb; pb = new Bitmap(*rhs.pb); delete pOrig; return *this; }
- copy and swap技术:使用拷贝构造函数生成副本cp,交换this与cp,返回this
12: Copy all parts of an object.
复制对象时勿忘其每一个成分
- 新增数据成员,需要修改拷贝构造函数、赋值运算符
- 继承子类的拷贝函数确保复制了所有父类成分
- 拷贝构造函数和赋值运算操作符的共同部分,应该放入统一的函数,而不是尝试互相调用