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

    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_casttypeid)。

    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的。避免将指向派生类对象 的派生类部分(即:未初始化的成员变量)。
      (第二点有待进一步理解。)
  • 相关阅读:
    FirstApp,iphone开发学习总结
    FirstApp,iphone开发学习总结7,相机
    FirstApp,iphone开发学习总结6,Navigation的使用
    FirstApp,iphone开发学习总结1,UIview添加UIimage
    oracle 12cR2 RAC deconfig CRS过程记录
    Activex word
    图片灰度化。
    vb word
    加密
    页面内打开word 组件。
  • 原文地址:https://www.cnblogs.com/lasnitch/p/12764199.html
Copyright © 2011-2022 走看看