【Effective_c++】条款09:绝不在构造和析构函数中调用virtual函数

来源:互联网 发布:像微领时代的软件 编辑:程序博客网 时间:2024/06/05 19:50

条款09:绝不在构造和析构函数中调用virtual函数

前言

绝不能在构造函数和析构函数期间不要调用virtual函数,虽然语法上并不会有错误。但是这个操作很有可能不是我们所期望的结果.

示例

拿书上例子来进行说明.

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; //怎样实现这种类型交易的登录? ...};

对于如上类的设计,Transaction 基类构造函数中调用了虚函数,我们设计的意图是当一个具体的派生类类型的类型被创建时候,根据它类型的不同,调用不同的logTransaction 函数.

但是如果我们执行如下语句的时候,具体会发生什么操作呢?

BuyTransaction b;  

我们在构造一个BuyTransaction类实例时。首先调用的是Transaction类的构造器, 派生类对象的基类部分是在派生类部分之前被构造的。Transaction构造器的最后一行调用了虚函数logTransaction,但是奇怪的事情正是在此发生的。被调用函数logTransaction的版本是Transaction中的那个,而不是BuyTransaction中的那个. 即使现在产生的对象的类型是BuyTransaction,情况也是如此。在基类的构造过程中,虚函数调用从不会被传递到派生类中。代之的是,派生类对象表现出来的行为好象其本身就是基类型

对于上面的这种行为, 我们一方面可以从这个方向去理解: 因为基类构造器是在派生类之前执行的,所以在基类构造器运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类,派生类对象当然可以参照引用局部的数据成员,但是这些数据成员其时尚未被初始化。容易产生各种安全问题. 沿类层次往下调用尚未初始化的对象的某些部分本来就是危险的,所以C++干脆不让你这样做。

更根本的在于, 在一个派生类被构造过程中, 他的类型是不断变化的, 换而言之, 在调用基类构造函数的时候, 此时对象的类型直接就是基类, 包括我们使用typeid或者dynamic_cast都会将对象类型视为基类类型,所以其调用的虚函数是永远不会下降到派生类的。

对于析构函数也有着类似的行为。一旦一个派生类的析构函数运行结束之后,该对象中的派生类数据成员就被假设为是未定义的值. 这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。。

对于上述问题的一种办法就是在Transaction中把函数logTransaction改变为一个非虚函数,然后要求派生子类的构造器要把必要的登录信息传递给Transaction的构造器。如此以来,上面的函数就能够安全地调用非虚函数logTransaction了。如下所示:

class Transaction {   public:    explicit Transaction(const std::string& logInfo);     void logTransaction(const std::string& logInfo) const;//现在是一个非虚函数    ...  };  Transaction::Transaction(const std::string& logInfo)  {   ...   logTransaction(logInfo);// 现在调用的是一个非虚函数  }   class BuyTransaction: public Transaction {   public:    BuyTransaction( parameters ):Transaction(createLogString(parameters)) { ... } //把登录信息传送给基类的构造函数    ...    private:    static std::string createLogString( parameters );  };  

  换句话说,既然在基类的构造函数中不能沿着类的继承层次往下调用虚函数,你可以通过在派生类中沿着类的层次结构把必要的构造信息传递到基类的构造器中来补偿这一点.

小结

不要在类的构造或者析构过程中调用虚函数,因为这样的调用永远不会沿类继承树往下传递到子类中去。我们在哪个类的构造函数或者析构函数中调用了虚函数, 就已经决定了这个虚函数只能调用当前类型对应的虚函数版本.

阅读全文
0 0