Item9: Never call virtual functions during construction or destruction

来源:互联网 发布:淘宝网怎么推广 编辑:程序博客网 时间:2024/06/01 09:04

你不该在构造函数和析构函数期间调用virtual函数。

 

现有一个class,建模股市交易如买进、卖出的订单等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志(audit log)中也需要创建一笔适当记录,下面是一个看起来颇为合理的做法:

 

当执行下面这行,会发生什么事情:

 

    在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至(resolve to)base class,若使用运行期类型信息(runtime type information,例如dynamic_cast(见条款27)和typeid),也会把对象视为base class类型。本例之中,当Transaction构造函数正执行打算初始化“BuyTransaction对象内的base class成分”时,该对象的类型是Transaction。That's how every part of C++ will treat it, and the treatment makes sense:这个对象内的“BuyTransaction专属成分”尚未被初始化,所以面对它们,最安全的做法就是视它们不存在。对象在derived class构造函数开始执行前不会成为一个derived class对象。

 

      相同的道理也适用于析构函数,一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。进入base class析构函数后对象就成为一个base class对象,而C++的任何部分包括virtual函数、dynamic_casts等等也就是那么看待它。

 

      在上述示例中,Transanction构造函数直接调用一个virtual函数,这很明显违反了本条款。但侦测“构造函数或析构函数运行期间是否调用virtual函数”并不总是这般轻松。如果Transaction有多个构造函数,每个都需执行某些相同工作,那么避免代码重复的一个优秀做法就是把共同的初始化代码(其中包括对logTransaction的调用)放进一个初始化函数如init内:

 

这段代码中由于logTransaction是Transaction内的一个pure virtual函数,当pure virtual函数被调用,大多执行系统会中止程序(通常会对此结果发出一个信息)。然而如果logTransaction是个正常的(impure)virtual函数并在Transaction内带有一份实现代码,该版本就会被调用,程序也会兴高采烈的继续执行,留下你百思不得其解为什么建立一个derived class对象会调用错误版本的logTransaction。The only way to avoid this problem is to make sure that none of your constructors or destructors call virtual functions on the object being created or destroyed and that all the functions they call obey the same constraint.

 

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

 

      其他方案可以解决这个问题。一种做法是在class Transaction内将logTransaction函数改为non-virtual,然后要求derived class构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全地调用non-virtual logTransaction。像这样:

 

 

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

 

     注意本例之BuyTransaction内的private static函数createLogString的运用。比起在成员初值列(member initialization list)内给予base class所需数据,利用辅助函数创建一个值传给base class构造函数往往比较方便(也比较可读)。令此函数为static,也就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。这很重要,正是因为“那些成员变量处于未定义状态”,所以“在base class构造和析构期间调用的virtual函数不可下降至derived classes”。

 

请记住:

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

Dont't call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of the currently executing constructor or destructor.