作者:LogM

本文原载于 https://segmentfault.com/u/logm/articles,不允许转载~

2. 构造/析构/赋值

  • 2.1 条款05:C++会自动编写default构造、拷贝构造、析构、赋值函数

    //你以为你写了个没有代码的空类class Empty{};// 实际上,C++自动生成了很多函数class Empty{public:    Empty() {...}    //默认构造函数    Empty(const Empty& rhs) {...}    //拷贝构造函数    ~Empty() {...}     //析构函数    Empty& operator=(const Empty& rhs) {...}    //赋值函数};
  • 2.2 条款06:声明为private防止自动生成的函数被使用

    //把拷贝构造函数和赋值函数声明为private类型,防止被使用class Empty {public:    ...private:    ...    Empty(const Empty&);    Empty& operator=(const Empty& rhs);};
  • 2.3 条款07:使用多态特性时,基类的析构函数必须为virtual

      //一个多态的场景  class TimeKeeper {   //计时器(基类)  public:      TimeKeeper();      virtual ~TimeKeeper();      ...  };  class AtomicClock: public TimeKeeper {...}    //原子钟  class WristWatch: public TimeKeeper {...}    //腕表  //往往这么使用多态特性  TimeKeeper* ptk = getTimeKeeper();  ...  delete ptk;

    上面是使用多态的一个场景,当delete ptk时,因为ptkTimeKeeper类型,如果基类析构函数不是virtual,那么ptk的析构时只调用基类析构函数,析构不正确;使用virtual ~TimeKeeper();保证ptk析构时调用正确的子类析构函数

    如果一个类带有virtual函数,说明这个类有可能用于多态,那么就应该有virtual析构函数。

    不要对non-virtual析构函数的类使用多态特性,包括string、vector在内的STL容器。

    带有virtual函数的类会生成vtbl (virtual table)用于在运行期间确定具体调用哪个函数,由vptr (virtual table pointer)指向,占用空间。胡乱声明virtual会增加体积。

  • 2.4 条款08:析构函数不要抛出异常

    • 析构函数抛出异常,程序终止析构,会导致资源没有完全释放。
        //管理数据库连接的典型场景    //如果这么写,析构函数有可能抛出异常    class DBConn {    public:        ...        ~DBConn() {            db.close();        }    private:        DBConnection db;    }    //在析构函数内捕捉异常,不要让析构函数的异常抛出    class DBConn {    public:        ...        ~DBConn() {            try { db.close(); }            catch (...) {                ...            }        }    private:        DBConnection db;    }
  • 2.5 条款09:不在构造和析构函数中使用virtual函数

    • 构造函数在构造时,派生类的成员变量未初始化完毕,virtual函数指向基类;
    • 析构函数在析构时,派生类的成员变量部分析构,virtual函数指向基类。
  • 2.6 条款10:令 operator= 返回值为 reference to *this

    • 这么做的目的是为了实现连续赋值
    int x, y, z;x = y = z = 1;   //连续赋值int& operator=(const int& rhs) {    ...    return *this;   //返回左侧对象的引用}
  • 2.7 条款11:在 operator= 中处理自我赋值

    //下面代码,如果出现自我赋值,则出错Widget& Widget::operator=(const Widget& rhs) {    delete elem;    elem = new Bitmap(*rhs.elem);    return *this;}//方法1Widget& Widget::operator=(const Widget& rhs) {    if (this == &rhs) { return *this; }    delete elem;    elem = new Bitmap(*rhs.elem);    return *this;}//方法2Widget& Widget::operator=(const Widget& rhs) {    Bitmap* pOrig = elem;    elem = new Bitmap(*rhs.elem);    delete pOrig;    return *this;}
  • 2.8 条款12:拷贝构造和赋值函数中不要遗漏成员变量

    • 编译器不检查拷贝构造和赋值函数是否对所有成员变量进行了拷贝;
    • 派生类的拷贝构造和赋值函数记得要先调用基类的拷贝构造和赋值函数。