条款9:绝不在构造函数和析构函数中调用virtual 函数
1、在构造函数中调用virtual 函数
先看段代码,书中的例子,修改了下
class Transaction
{
public:
Transaction() {LogTransaction();}
virtual void LogTransaction()
{
cout<<"Trasaction Log \n";
}
};
class BuyTransaction : public Transaction
{
public:
void LogTransaction()
{
cout<<"BuyTransaction Log \n";
}
};
调用
BuyTransaction b;
结果Trasaction Log
通过结果可以看出不是我们想要的。BuyTransaction b,这里BuyTransaction 构造函数会被调用,但会先调用基类Trasaction的构造函数,但该构造函数里面调用一个virtual
函数,意向是调用子类的LogTransaction函数,但实际上子类还没有开始构造,即对象还没有在内存中形成,对象的所有成员还没有定义,因此编译器不会让这样不可意料的事情发生,规定:base class构造期间virtual函数绝对不会下降到derived class型阶层,即在该阶段对象时隶属base class的,调用的virtual 函数不是virtual 函数。
在书中还有另一种说法,在derived class对象的base class构造期间,对象的类型是base
class而不是derived class。不只virtual函数会被编译器解析至(resolve to)base class,若使用运行期类型信息,也会把对象视为base class类型。因为“derived 专属成分”尚未初始化,因此:对象在derived
class构造函数开始执行前不会成为一个derived class对象。
2、在析构函数中调用virtual 函数
其结果和解释都可以跟上面一样,基类的析构函数中调用virtual 函数,结果还是调用基类的函数,因为析构函数的行为时:先调用子类的析构函数,再到基类,子类析构后,子类的所有成员均为未初始化,因此,进入基类的析构函数后对象就成为一个基类对象。
如何解决上面的问题呢?将virtual
函数修改为non-virtual 函数,在子类中构造函数中传入对应的值。
class Transaction
{
public:
Transaction(const std::string &strLog) {LogTransaction(strLog);}
void LogTransaction(const std::string &strLog)
{
cout<<"Trasaction Log :"<<strLog<<endl;
}
};
class BuyTransaction : public Transaction
{
public:
BuyTransaction(const std::string &strLog) : Transaction(strLog)
{
}
};
记住
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived
class(比起当前执行构造和析构函数的那层)。