乐趣区

读书笔记Effective-C02构造析构赋值

作者: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;
    }
    
    // 方法 1
    Widget& Widget::operator=(const Widget& rhs) {if (this == &rhs) {return *this;}
    
        delete elem;
        elem = new Bitmap(*rhs.elem);
        return *this;
    }
    
    // 方法 2
    Widget& Widget::operator=(const Widget& rhs) {
        Bitmap* pOrig = elem;
        elem = new Bitmap(*rhs.elem);
        delete pOrig;
        return *this;
    }
  • 2.8 条款 12:拷贝构造和赋值函数中不要遗漏成员变量

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