您的位置:首页 > 其它

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

2009-06-09 14:12 330 查看
条款09:绝不在构造和析构过程中调用virtual函数
(Never call virtual functions during construction or destruction.)

内容:
我以一个书上的例子开始:一个正规一点的大型超市一般都有一个管理进货、卖出、订货等交易动作
的管理系统,该系统必须维护一个交易操作记录,方便查询,故每进行一笔交易时,需要把此项交易信息log
入数据管理系统进行存储,这里我们有了一个交易类Transaction,但交易有很多种类型所以我们log它们的
时候需要virtual函数来实现,故你很容易写出如下代码:
class Transaction{
public:
Transaction(){
...
logTransaction(); //记录此笔交易
}
virtual void logTransaction()const=0;
...
};
class BuyTransaction:public Transaction{
public:
virtual void logTransaction()const;
...
};
class SellTransaction:public Transaction{
public:
virtual void logTransaction()const;
...
};
这样你开始写下这样的执行代码,然后compile它:
BuyTransaction buy; //error LNK2019: unresolved external symbol "public: virtual void __thiscall
//Transaction::logTransaction(void)const " (?logTransaction@Transaction@
//@UBEXXZ) referenced in function "public: __thiscall Transaction::
//Transaction(void)" (??0Transaction@@QAE@XZ)
晕,这啥玩意?是不是编译器坏了?呵呵,不要急.我们来看看是你的问题还是编译器的问题,首先我们来
看看buy这个对象的构造过程,注意构造顺序是:先构造基类,然后才是子类,当执行基类的构造函数中的
logTransaction()方法时,该调用哪个版本的logTransaction呢?编译器这个时候调用的是基类版本
Transaction::logTransaction,因为这个时候子类对象压根还没有被构造出来,这个时候编译器认为当前的
对象类型是base class而不是derived class,不仅仅virtual函数被解析至base class,就算使用运行期类
型信息(runtime type information)比如:dynamic_cast和typeid,也会把它当做base class类型的.
这样的解释能看懂了么?如果还是看不懂,那么我们来用反证法论证它最终调用的就是base的版本.假设
基类构造函数调用子类版本logTransaction(此例子中为:BuyTransaction::logTransaction),那么此时的子
类对象的成员变量还没有被初始化,而子类版本的logTransaction内部则就使用了未被初始化的变量,C++编
译器不会容许你这么做的,原因很简单因为使用未初始化变量会承担"出现不明确定义"的巨大风险(条款:04).
同样的道理,析构函数也不能用virtual函数,对象析构函数的顺序与构造的顺序相反,当调用析构基类的
析构函数时,子类已经销毁,那么它就没有就会调用子类版本的virtual函数.
现在我们已经知道不能在构造析构函数中调用virtual函数,那么如何避免这种现象的发生,这可不是一件
好差事,例如你可以在你的构造函数中用一个中间函数,而此中间函数实现中却调用了virtual函数,这样的调
发式编译器是检测不出来的.而如果这个virtual函数是pure virtual且没提供默认实现代码时,当你屁颠屁颠
的运行这个程序,当这个函数被调用时,大多数执行系统会终止程序,弄的你丈二和尚摸不着头脑;而如果你这个
virtual函数已经有了一份实现代码,那么该版本就会被调用,而你的程序也顺利地大步向前走,正当你准备高兴
之余你却突然发现它调用的不是子类的版本virtual函数,而留下你一人在作痛苦状思考:为什么它会调用错误
的virtual版本呢?要是你连"编译器调用了错误的函数版本"都没检测出来的话,那你就等着你自己的心理防线彻底崩
溃吧?
那我该这么办啊?我偏要达到我的目的,让基类执行子类的实现代码?这个问题有解决方案么?好,我们再来看
一下这个问题,既然我们不能让基类调用子类virtual函数版本,那么我们可以把子类的virtual函数所要实现的
功能转交给基类来完成不就可以了嘛,想法很好!具体做法是:将基类的virtual函数变为non-virtual函数,然后
在子类的构造函数当中传递特定信息给基类,让基类来完成子类的功能.
实现代码可以这样修改:
class Transaction{
public:
explicit Transaction(const std::string& logInfo){
...
logTransaction(logInfo);
}
void logTransaction(const std::string& logInfo)const{
}
};
class BuyTransaction:public Transaction{
public:
BuyTransaction(Parameters):Transaction(createLogString(Parameters)){
}
private:
static std::string createLogString(Parameters)const;
};
今天就讲到这里了,打字真TNND累人!

请记住:
■ 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函
数和析构函数的那层).
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐