构造/析构/赋值运算 条款05 了解C++默默编写并调用哪些函数 条款06 若不想使用编译器自动生成的函数,就该明确拒绝 条款07 为多态基类声明vritual析构函数 条款08 别让异常逃离析构函数 条款09 绝不在构造和析构过程中调用vritual函数 条款10 令operator=返回一个reference to *this 条款11 在operator= 中处理“自我赋值” 条款12 复制对象勿忘其每一个成分

  • 一个empty class编译器自动生成:default构造函数、copy构造函数、析构函数、copy赋值函数。

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

所有编译器产生的函数都是public,为阻止这些函数被创建出来,你得自行声明它们。可以将copy构造函数或赋值操作符声明为private,并不予实现,既可以阻止编译器暗自创建它们,又可以成功阻止人们调用它。

  • 为阻止不要去自动生成函数功能,可将相应的成员函数声明为private并且不予实现。

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

  • 带多态性质的基类应该声明一个vritual析构函数。如果class带有任何vritual函数,它就应该拥有一个vritual析构函数。
  • class的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明vritual析构函数

当派生类对象经由一个基类指针被删除,而该基类带着一个non-vritual析构函数,会造成一个“局部销毁”对象,形成内存泄漏等问题。

欲实现出vritual函数,对象必须携带vptr信息,主要用来在运行期决定哪个vritual函数该被调用。vptr(vritual table pointer)指针。vptr只想一个由函数指针构成的数组,成为vtbl虚函数表;每个带有vritual函数的类都用一个对应的虚函数表。当对象调用某一vritual函数,实际被调用的函数取决于该对象的虚函数表指针所指的那个虚函数表。

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

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

条款09 绝不在构造和析构过程中调用vritual函数

  • 在构造和析构期间不要调用vritual函数,因为这类调用从不下降至派生类
class Transaction
{
public:
    Transaction();
    vritual void logTransaction() const =0;
    ...
};
Transaction::Transaction()
{
    ...
    logTransaction();
}

class BuyTransaction:public Transaction
{
public:
    vritual void logTransaction() const;
    ...
}
class SellTransaction:public Transaction
{
public:
    vritual void logTransaction() const;
    ...
}

此时,BuyTransaction b;
这时候调用的logTransaction函数是基类的版本。因为基类构造函数先执行。

解决方法,在基类中将logTransaction函数改为non-vritual函数,然后要求派生类构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全地调用non-vritual的logTransaction函数

class Transaction
{
public:
    explicit Transaction(const std::string& logInfo);
    vritual void logTransaction(const std::string& logInfo) const;//如今是个non-vritual函数
    ...
};
Transaction::Transaction(const std::string& logInfo)
{
    ...
    logTransaction(logInfo);
}

class BuyTransaction:public Transaction
{
public:
    BuyTransaction(parameters)
    :Transaction(createLogString(parameters)){...}//将log信息传给基类构造函数
    ...
private:
    static std::string createLogString(parameters);
};

注意本例派生类中的私有static函数createLogString(parameters),比起在成员初始化列表内给予积累所需数据,利用辅助函数创建一个值给基类构造函数往往比较方便和可读。

条款10 令operator=返回一个reference to *this

为实现“连锁赋值”,重载复制操作符必须返回一个reference指向操作符的左侧实参。

class Widget
{
public:
    Wiget& operator=(const Widget& rhs)//返回类型是个引用,指向当前对象
    {
    ...
    return* this;//返回左侧对象
    }
}

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

  • 确保当对象自我赋值是opreator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap
    一个正确的做法
class Bitmap{...};
class Widget
{
...
void swap(Widget& rhs);//交换*this和rhs的数据
...
private:
    Bitmap* pd;
};
Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);//为rhs数据制作一份副本
    swap(temp);//将*this数据和上述副本的数据交换
    return *this;
}

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

  • copying函数应该确保复制“对象内的所有成员变量”以及“所有基类成分”
  • 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

设计良好的面向对象程序会将对象的内部封装起来,只留两个函数负责对象拷贝,copy构造函数和重载赋值操作符,我们称它们为copying函数。

重载复制操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象。
反过来,copy构造函数调用复制操作符同样无意义。构造函数用来初始化对象,而复制操作符只施行于已初始化对象身上。