effective C++ 条款 9:绝不在析构和构造函数中调用virtual函数

每当创建一个交易对象,在审计日志中也要创建一笔适当记录。下面是一个看起来比较合理的做法:

class Transaction
{
public:
    Transaction()
    {
        init();
    }
    virtual void logTransaction() const { std::cout << "transaction" ;};
protected:

private:
    void init()
    {
        logTransaction();
    }
};

class BuyTransaction : public Transaction
{
public:
    BuyTransaction():a(5){}
    virtual void logTransaction() const { std::cout << "BUytransaction"<< a;};
protected:
private:
    int a;
};

class SellTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
protected:
private:
};

inline void testItem9()
{
    BuyTransaction b;
}

base class构造期间virtual函数绝不会下降到derived classes阶层。取而代之的是,对象的作为就像隶属base类型一样。

非正式的说法:在base class构造期间,virtual函数不是virtual函数。

由于base class 的构造函数的执行更早于derived class构造函数,当base class的构造函数执行时,derived class的成员变量尚未初始化。如果期间调用的virtual函数下降至derived class阶层, derived class的函数几乎必然取用local成员变量,而那些成员变量尚没有初始化。这会导致不明确行为和彻夜调试。

其实还有更根本的原因,在derived对象的base class 构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息(runtime type information,例如dynamic_cast和typeid),也会把对象视为base class类型。本例中当Transaction构造函数正执行起来打算初始化“BuyTransaction对象内的base class成分”时,该对象的类型是Transaction。这个对象内的“BuyTransaction专属成分”尚未被初始化,所以面对这些,最安全的做法是视他们不存在。对象在derived class构造函数开始执行之前不会成为一个derived class对象

相同道理也适用于析构函数。一旦derived class 析构函数开始执行, 对象内的derived class成员变量便呈现未定义值,

所以c++视他们仿佛不存在,进入base class析构函数后,对象就变成一个base class对象。

如何确保每次一有Transaction继承体系上的对象被创建,就会有适当版本的logTransaction被调用呢?在构造函数中调用virtual函数是一种错误的做法。

其他方案可以解决这个问题。一种做法是在Transaction内将logTransaction修改为non-virtual,然后要求derived class 构造

函数传递必要的信息给Transaction构造函数, 然后那个构造函数便可安全的调用non-virtual的logTransaction。像这样:

class Transaction
{
public:
    explicit Transaction(const std::string &logInfo);
    void logTransaction(const std::string &logInfo) const;
protected:

private:
};
Transaction::Transaction(const std::string &logInfo)
{
    logTransaction(logInfo)
}

class BuyTransaction : public Transaction
{
public:
    BuyTransaction(parameters)
        : Transaction(createLogString(parameters))
    {
        ...;
    }
protected:
private:
    static std::string createLogString(parameters);
    int a;
};

换句话说由于你无法使用virtual函数从base class向下调用,在构造期间,你可以藉由“令derived class将必要的构造信息向上传递至base class构造函数”替换之而加以弥补。

注意本例之BuyTransaction内的private static函数createLogString的运用。是的,比起在成员初始化列表里给予base class

所需数据。利用辅助函数创建一个值传给base class构造函数往往比较方便。令此函数为static,也就不可能意外指向“初值未成熟的BuyTransaction对象内尚未初始化的成员变量”。

在构造函数和析构函数期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造和析构的那层)。

posted @ 2012-01-13 15:53  lidan  阅读(591)  评论(0编辑  收藏  举报