1、为什么不能在构造函数中调用virtual函数?
(1)考虑下面的场景:
class Transaction { //所有交易的base class
public:
Transaction();
virtual void logTransaction() const = 0; //做出一份因为类型不同而不同的日志记录,目前是一个纯虚函数
...
};
Transaction::Transaction() //base class的构造函数的实现
{
...
logTransaction(); //最后的动作是对这笔交易进行记录,调用了virtual函数
}
class BuyTransaction: public Transaction { //derived class
public:
virtual void logTransaction() const; //对这种类型的交易进行记录(log)
...
};
class SellTransaction: public Transaction { //derived class
public:
virtual void logTransaction() const; //对这种类型的交易进行记录(log)
...
};
即在构造函数中调用了virtual函数。
(2)现象
当执行下列代码时:
BuyTransaction b;
将会引发一个异常:
由于继承自Transaction的BuyTransaction类型的变量b 在创建时会首先创建其基类部分(Transaction),也就是会调用Transaction的默认构造函数,而在构造函数中调用了一个virtual 的函数logTransaction()
,本身b是一个BuyTransaction的对象,但是在构造其基类部分时,调用的logTransaction()
函数却是Transaction类版本的。而我们预想的应该是BuyTransaction类版本的logTransaction()
函数才对。即:在构造函数中的virtual并没有起到多态的效果,而是直接使用所在类版本的函数。
2、上述异常产生的原因是什么?
- 构造一个派生类对象时,首先构造其基类部分,其次构造其派生类部分。而当构造基类部分时,派生类部分的成员变量还没有被初始化,因此,如果此时,调用派生类版本的成员函数的话,很有可能用到没有初始化的派生类部分的成员变量,这在C++中显然会导致不明确行为。因此,C++编译器拒绝你这么做,因此它错误的调用所在类版本的函数,而不产生多态效果。
- 在派生类对象的基类部分构造期间,对象的类型是基类类型,而不是派生类类型。不仅virtual函数被解析至
(resolve) to base class
,运行期类型信息也会被解析成(resolve) to base class(
比如:dynamic_cast
和typeid
)。
3、为什么在析构函数中不能够调用virtual函数?
析构函数从外向里析构,也就是先执行派生类部分,再执行基类部分。如果基类部分中调用了派生类版本的virtual成员函数,此时派生类部分已经被析构了,因此类似于构造函数,仍是会造成不确定行为。因此当析构函数执行到里层时,仍是被编译器看做是一个基类类型的对象。
4、如何检测析构函数或者构造函数是否运行期间调用了virtual成员函数?
情况一:在构造函数或者析构函数中调用的virtual成员函数是pure的,那么会导致链接错误。因为找不到纯虚函数的实现代码。
情况二:在构造函数或者析构函数中调用的virtual成员函数是非pure的,那么编译器不会报任何错误。但是导致的结果却是我们所不想看到的。
因此,我们值能通过不在构造函数和析构函数中调用virtual成员函数来避免这个问题发生。
5、既然不能够在构造函数和析构函数中调用virtual成员函数,如何解决上述,类似于记录不同派生类对象的日志问题?
- 将日志信息函数写成非virtual的。
- 此时可以在构造函数中调用日志信息函数。
- 但是为了确保不同的派生类对象生成的是不同版本的日志信息,需要在构造函数中多传递一个参数,以确定产生的是哪个类型的日志信息。
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std:;string& logInfo) const;//此时,是一个non-virtual函数
...
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo); //此时,是一个non-virtual调用
}
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction(createLogString( parameters )) //将log信息传递给base class构造函数
{ ... }
...
private:
static std::string createLogString( parameters );//注意点
};
这段程序有两个点注意:
- 利用辅助函数代替成员初值列给base class传递构造信息,比较方便,可读性也好。
- 将这个函数写成static的。避免将指向派生类对象 的派生类部分(即:未初始化的成员变量)。
(第二点有待进一步理解。)