1️⃣ 案例引入

  • 假设你有个class继承体系,用来塑模股市交易如买进、卖出的订单等等。这样
    的交易一定要经过审计,所以每当创建一个交易对象,在审计日志(audit log)中也
    需要创建一笔适当记录。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Transaction {     //所有交易的 base class
    public:
    Transaction();
    virtual void logTransaction() const = 0; //做出一份因类型不同而不同
    //的日志记录(log entry)
    };

    Transaction::Transaction() // base class构造函数之实现
    {
    ···
    logTransaction(); // 最后动作是志记这笔交易
    };

    class BuyTransaction: public Transaction //derived class
    {
    public:
    virtual void logTransaction() const; //志记(log)此型交易
    };

    class SellTransaction: public Transaction //derived class
    {
    public:
    virtual void logTransaction() const; //志记(1og)此型交易
    };
  • 现在,当以下这行被执行,会发生什么事 ?
    1
    BuyTransaction b;
  • 🥵执行过程
    • BuyTransaction构造函数被调用
    • 首先Transaction构造函数一定会更早被调用———derived class对象内的base class成分会在derived class。自身成分被构造之前先构造妥当。
    • Transaction构造函数的最后一行调用virtual函数logTransaction,这正是引发惊奇的起点。
      • 这时候被调用的logTransactionTransaction内的版本,不是BuyTransaction内的版本———即使目前即将建立的对象类型是BuyTransaction。是的,baseclass构造期间virtual函数绝不会下降到derived classes阶层。取而代之的是,对象的作为就像隶属base类型一样。
      • 非正式的说法或许比较传神:在base class构造期间,virtual函数不是virtual函数。这一似乎反直觉的行为有个好理由。由于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时derived class的成员变量尚未初始化。如果此期间调用的virtual函数下降至derived classes阶层,要知道derived class的函数几乎必然取用local成员变量,而那些成员变量尚未初始化。
  • 🤓根本原因
    • derived class对象的base class构造期间,对象的类型是base class而不是derived class
    • 不只virtual函数会被编译器解析至base class,若使用运行期类型信息(例如dynamic_casttypeid),也会把对象视为base class类型。

2️⃣ 同理析构函数

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