zoukankan      html  css  js  c++  java
  • Effective C++_笔记_条款09_绝不在构造和析构过程中调用virtual函数

       (整理自Effctive C++,转载请注明。整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/

    为方便采用书上的例子,先提出问题,在说解决方案。

    1 问题

       1: class Transaction{
       2: public:
       3:     Transaction();
       4:     virtual void LogTransaction() const = 0 ;
       5:     ...
       6: };
       7:  
       8: Transaction::Transaction()                //Base class 的构造函数之实现
       9: {
      10:     ...
      11:     LogTransaction();                    //最后动作是调用LogTransaction函数
      12: }
      13:  
      14: class BuyTransaction:public Transaction{
      15: public:
      16:     virtual void LogTransaction() const ; 
      17:  
      18: };

        假设在程序中:

       1: BuyTransaction b ;

        BuyTransaction构造函数会被调用,但首先Transaction的构造函数会被调用;derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当。Transaction构造函数最后一行调用virtual函数LogTransaction是base class内的版本。是的,base class构造期间virtual函数绝对不会下降到derived class阶层。即“在base class 构造期间,virtual函数不是virtual函数”。

        这一似乎反直觉的行为有个好理由:对于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时derived class的成员变量尚未初始化。如果此期间调用virtual函数下降至derived class阶层,要知道derived class的函数几乎必然取用local成员变量,而那些变量尚未初始化,C++不允许你这样做。

        其实还有更根本的原因:在derived class对象的base class构造期间,对象的类型是derived class。

        相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class 成员变量便呈现未定义的值。进入base class析构函数后对象就成为一个base class对象,而virtual函数等也那么看待它。

        另外,侦测“构造函数或析构函数运行期间是否调用virtual函数”并不是很轻松。唯一能够避免此问题的做法就是:确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。

    2 解决方案

        上面提到唯一能够避免此问题的做法就是:确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。

    但是,如何确保每次一继承体系上的对象被创建,都有适当版本的函数被调用呢?很显然,在base class构造函数内对着对象调用virtual函数是一种错误的做法。其他方案可以解决这个问题:一种做法是在class Transaction内将LogTransaction函数改为non-virtual,然后derived class构造函数传递必要的信息给Transaction构造函数,然后那个构造函数便可安全调用non-virtual LogTransaction:

       1: class Transaction{
       2: public:
       3:     explicit Transaction ( const string& logInfo ) ;
       4:     void LogTransaction ( const string& logInfo ) const ;//如今是个non-virtual函数
       5:     ...
       6: };
       7: Transaction::Transaction( const string& logInfo )
       8: {
       9:     LogTransaction(logInfo);                //如今是个non-virtual 调用
      10: }
      11:  
      12: class BuyTransaction:public Transaction{
      13: public:
      14:     BuyTransaction( parameters ):Transaction(createLogString(parameters))  //将log信息传递给base class构造函数
      15:     {...}
      16:     ...
      17: private:
      18:     static string createLogString(parameters) ;
      19: };
      20:  

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

    令createLogString为static,也就不可能意外指向“初期未成熟之BuyTransaction对象尚未初始化的成员变量”。这很重要,正因为“那些成员变量处于未定义状态”,所以在“base class构造和析构期间调用的virtual函数不可下降至derived class” 。

        请注意:在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class。

  • 相关阅读:
    判断DataSet为空
    msxml3.dll 错误 '800c0008'
    google Map api地理位置坐标转换
    C# .net中cookie值为中文时的乱码解决方法
    windows pear 安装
    smarty2 设置、变量、函数
    简单模板类
    mysql 1366 插入错误
    Oracle修改账户口令
    C# Winform验证码
  • 原文地址:https://www.cnblogs.com/hust-ghtao/p/3790071.html
Copyright © 2011-2022 走看看