[effective c++] 构造析构

条款7, 为多态基类声明virtual析构函数
Car *c = new YellowCar();
delete c;

c指向了一个YellowCar的对象,但c是一个Car指针,如果Car的析构函数不为虚函数,那么delete时,只会调用Car的析构函数。如果YellowCar中有动态申请的内存,这块的释放逻辑通常在它的析构函数来完成,那么delete时,无法调用到YellowCar的析构,所以就造成了内存泄漏。
不过要注意,如果没有用到多态,也就是说基类中没有虚函数,而把基类的析构函数声明为虚,这种做法是不好的。一来,虚函数需要通过虚函数表指针访问,虚表指针要占对象的空间。二来,类型转换上会带来不确定性。所以,没必要画蛇添足。

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

反例如下,当 doSomething 函数执行完毕的时候,会开始析构容器 v 中存放的各个 Widget 对象。然而,该对象析构函数可能会吐出异常。在这种情况下,将会吐出容器中对象个数的异常,这就会导致程序运行的不确定性

class Widget {
    
    
public:
    //......
    ~Widget() {
    
        // 假定这个析构函数可能会吐出异常
        //......
    }
    //......
};

void doSomething()
{
    
    
    //......
    std::vector<Widget> v;
    //......
}

正确使用的方法,在析构中处理异常,而不是让它抛出

// 修改后的DBConn类实现
class DBConn {
    
    
public:
    //......
    void close() {
    
        // 要求用户自己关闭数据库对象
        db.close();
        closed = true;
    }
    //......
    ~DBConn() {
    
    
        if (!closed) {
    
        // 如果用户忘记了这么做,就采用 try catch 机制吞下异常。
            try {
    
    
                db.close();
            }
            catch (...) {
    
    
                // 记录此次 close 失败
                //......
            }
        }
    }
    //......
private:
    //......
    DBConnection db;
    bool closed;    // 增设此变量用以判断用户是否已经自行调用 close(),用户也可根据此变量判断 close() 是否顺利执行并作出相应的异常处理。
    //......
};
条款9,不在构造和析构函数中调用虚函数
class Transaction {
    
    // 所有交易的基类
public:
 Transaction();
 virtual void logTransaction() const = 0;//建立依赖于具体交易类型的登录项
 ...
};
Transaction::Transaction() //实现基类的构造函数
{
    
     
 ...
 logTransaction(); //最后,登录该交易
} 
class BuyTransaction: public Transaction {
    
     
// 派生类
public:
 virtual void logTransaction() const; //怎样实现这种类型交易的登录? 
 ...
};
class SellTransaction: public Transaction {
    
     
//派生类
public:
 virtual void logTransaction() const; //怎样实现这种类型交易的登录?
 ...
};

在基类的构造过程中,虚函数调用从不会被传递到派生类中。代之的是,派生类对象表现出来的行为好象其本身就是基类型。通俗的讲,在基类的构造过程中,虚函数并没有被"构造"。
因为基类构造器是在派生类之前执行的,所以在基类构造器运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类,派生类对象当然可以参照引用局部的数据成员,但是这些数据成员其时尚未被初始化。这将会导致无休止的未定义行为和彻夜的代码调试。
一旦一个派生类的析构器运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。

猜你喜欢

转载自blog.csdn.net/niu91/article/details/112862868