item05 了解C++默默编写并调用哪些函数
对于一个空类,编译器可能默默生成下面四个函数。
class A {};class A {public:A() {...}A(const A& rhs) {...}~A() {...}A& operator=(const A& rhs) {...}};
:::info
- 构造函数
 - 拷贝函数
 - 析构函数
 - copy assignment
 
这些函数都是public且inline ::: :::tips 编译器生成的copy函数只是简单的将源对象的每一个non-static成员拷贝到目标对象
- 若是成员自己没有拷贝运算符,那么就会报错
 因此对于引用和const成员,应该定义自己的copy :::
item06 若不想使用编译器自动生成的函数,就应该明确拒绝
编译器生成的函数时public,若是想阻止生成,最简单的方式就是将函数显示地声明为private
- 但是若是在成员函数中或者是友元函数中调用拷贝,链接器会报错而非编译器报错;
:::tips
把报错转移到编译时期的方式:
利用一个专门为了阻止copy的base class ::: ```cpp class Uncopyable { protected: Uncopyable(); ~Uncopyable(); private: Uncopyable(const Uncopyable&); Uncopyable& operator=(const Uncopyable&); }; 
class A : private Uncopyable { … };
:::info任何人企图拷贝该对象时,编译器会尝试自动生成相关拷贝函数,而默认生成的拷贝构造函数会调用基类的版本,因此编译器会拒绝:::<a name="HeD51"></a># item07 为多态基类声明virtual析构函数若是base class的析构函数不是虚函数:::danger若是一个derived class对象经由一个base class的指针删除,那么只能删除base class的部分。:::- 消除方法是给base class一个virtual的析构函数。或者说如果class如果带有一个virtual函数,那么他也需要一个virtual的析构函数- class如果不是作为base class使用或者不是为了实现多态性,就不应该声明virtual的虚构函数。<a name="P5Jaq"></a># item08 别让异常逃离析构函数如果在析构过程中抛出异常,可能会导致资源释放不完全。<a name="WxCcF"></a>## 方法一:明确终止或吞掉异常```cpp~DBManager() {try { db.close(); }catch (...) {log();std::abort(); // 明确终止}}~DBManager() {try { db.close(); }catch (...) {log(); // 直接吞掉异常}}
方法二:将析构函数中可能吐出异常的操作提取到普通函数,给用户自行处理异常的机会
class DBManager {public:...~DBManager() {if (!closed) {try {db.close();}catch (...) {log();}}}void close() // 由客户显式调用,自行处理异常{db.close();closed = true;}private:DBConnection db;bool closed;}
item09 绝不在构造和析构过程中调用virtual函数
在构造和析构过程中调用virutal函数会带来意想不到的效果。
class Transaction{public:Transaction();virtual void log() const=0;};Transaction::Transaction(){//...log();}//------------------------------class BuyTransaction:public Transaction{public:virtual void log() const;};//------------------------------class SellTransaction:public Transaction{public:virtual void log() const;};
若是执行:BuyTransaction b
- 首先执行基类的构造函数
 - 构造函数最后会调用log;
 - 但是这时派生类还没构造,因此调用的会是基类的log :::danger 在构造或是析构中调用虚函数,此时虚函数并不是虚函数! ::: 析构函数也类似,一旦开始执行析构函数derived class会先被释放,因此虚函数会无效
 
item10 令operator=返回一个reference to *this
为了实现“连锁赋值”,需要令赋值操作符返回一个操作符左侧实参的引用
item11 在operator=中实现“自我赋值”
class Widget { ... };Widget w;...w = w; // 赋值给自己// 潜在的自我赋值a[i] = a[j];// 潜在的自我赋值*px = *py;// 潜在的自我赋值class Base { ... };class Derived : public Base { ... };void doSomething (const Base* rb, Derived* pd); // rb 和 pd 可能指向同一对象
问题一 自我赋值安全性
源和目的可能是同一对象,此时 delete pb 将会销毁当前对象的 bitmap,导致返回后该指针未定义,一种修改方式如下
Widget& Widget::operator=(const Widget& rhs){if (rhs == *this) return *this; // 证同测试delete pb;pb = new Bitmap(*rhs.pb);return *this;}//添加“证同测试”使得其具备“自我赋值”安全性,但是仍然存在问题二。
问题二 异常安全性
即使按照问题一的修改方式,仍可能存在问题,如果 new Bitmap 时发生异常,将会导致 pb 失效,一种简单的修改方式如下
Widget& Widget::operator=(const Widget& rhs){Bitmap* pOrig = pb;pb = new Bitmap(*rhs.pb);delete pOrg;return *this;//复制pb所指的东西前不要删除pb}
即使没有“证同测试”,这种修改方式也已经同事解决了问题一。如果比较关心效率,可以在加上“证同测试”,此时需要从效率上衡量必要性
另一种修改方式是 copy and swap 技术
class Widget{...void swap(Widget* rhs);...};Widget& Widget::operator=(const Widget& rhs){Widget temp(rhs); // 为 rhs 制作一份副本swap(temp); // 将 *this 与上述副本交换return *this;}
item12 复制对象时勿忘每一个成分
如果不使用编译器生成的copy函数,那么会出现的问题是: :::danger 如果遗漏了某个成员的复制,编译器不会报错! :::
- 复制所有局部成员变量
 - 调用所有base class的copy函数 :::tips 如果发现copy函数和copy assignment有相似的代码,消除重复代码的方式是建立一个新的成员函数给二者使用。 :::
 
