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的。避免将指向派生类对象 的派生类部分(即:未初始化的成员变量)。
      (第二点有待进一步理解。)
  • 相关阅读:
    hdu 4027 Can you answer these queries? 线段树
    ZOJ1610 Count the Colors 线段树
    poj 2528 Mayor's posters 离散化 线段树
    hdu 1599 find the mincost route floyd求最小环
    POJ 2686 Traveling by Stagecoach 状压DP
    POJ 1990 MooFest 树状数组
    POJ 2955 Brackets 区间DP
    lightoj 1422 Halloween Costumes 区间DP
    模板 有源汇上下界最小流 loj117
    模板 有源汇上下界最大流 loj116
  • 原文地址:https://www.cnblogs.com/lasnitch/p/12764199.html
Copyright © 2011-2022 走看看