• Effective C++(9) 构造函数调用virtual函数会发生什么


    问题聚焦:
    不要在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果。

    让我先来看一下在构造函数里调用一个virtual函数会发生什么结果
    Demo

    class Transaction {
    public:
     Transaction();
     virtual void logTransaction() const = 0;
    };
    Transaction::Transaction()
    {
     logTransaction();
    }
    //void Transaction::logTransaction() const      //第一次编译时,注释掉该段代码
    //{
    //    std::cout << "Transaction called" << std::endl;
    //}
    class BuyTransaction: public Transaction
    {
    public:
     virtual void logTransaction() const;
    };
    void BuyTransaction::logTransaction() const
    {
     std::cout << "BuyTransaction called" << std::endl;
    }
    
    //执行下面的语句会发生什么事情?
    BuyTransaction b;


    执行上述代码,会发现链接出错,报错:



    表示没有Transaction::logTransaction() const的实现。
    现在把上述代码中被注释的代码补上之后,再编译一次,OK,通过了。来看看结果是什么吧

     

    不错,运行正常,也没有crash。不过,貌似不是我们想要的结果。
    我们是对BuyTransaction类进行实例化,但是构造函数调用的是父类的构造函数,这是为什么呢?

    下面我们来分析一下原因:
    1. 父类构造函数先调用,此时子类的局部成员变量还没准备好,这时如果将virtual函数下降至子类阶层,那么使用未初始化的部分可能会引起不明确的行为,所以C++直接禁止了这种行为,因此会出现上面的链接错误。
    2. 在子类的父类部分被构造期间,virtual函数并不认为是virtual函数,因为在初始化子类的父类部分时,编译器认为当前的对象是父类型,同时,直接认为子类部分是不存在的,因为子类成分并没有被初始化。

    问题很明显了,就是在构造函数和析构函数中调用了virtual函数导致了上面的问题。那么有什么解决方案呢?
    解决方案:令子类将必要的构造信息向上传递至base class构造函数
    Demo

    /** main.h **/
    #include<iostream>
    #include<string>
    class Transaction {
    public:
     explicit Transaction(const std::string& logInfo);
     void logTransaction(const std::string& logInfo) const;   // 不再是虚函数
    };
    Transaction::Transaction(const std::string& logInfo)
    {
     logTransaction(logInfo);
    }
    void Transaction::logTransaction(const std::string& logInfo) const
    {
     std::string s = logInfo;
     std::cout << s << std::endl;
    }
    class BuyTransaction: public Transaction
    {
    public:
     BuyTransaction(const std::string& logInfo);
    };
    BuyTransaction::BuyTransaction(const std::string& logInfo):Transaction(logInfo)    // 把信息传递给父类,通过父类打印出log
    {
     std::cout << "BuyTransaction called" << std::endl;
    }
    
    /** main.cpp **/
    #include<iostream>
    #include"main.h"
    int main()
    {
     BuyTransaction b("hello world");
     system("Pause");
     return 0;
    }


    打印结果:

     

    这是我们想要的结果。

    小结:
    在构造和析构函数内不要调用virtual函数,因为这类调用不下降至子类。


    参考资料:
    《Effective C++ 3rd》


  • 相关阅读:
    视图&索引&序列
    PL/SQL语言基础
    C#实现递归矩阵连乘(动态规划的递归自顶向下,非递归自地向上)
    JS 第五课
    请问,小丽的鞋子到底是什么颜色的?
    用10!来回顾 PL/SQL语言基础 && 标准异常名
    SELECT 查询—子查询
    备份和恢复
    JS 第六课
    Linux学习之二Linux系统的目录结构
  • 原文地址:https://www.cnblogs.com/suzhou/p/3638963.html
Copyright © 2020-2023  润新知