C++ 派生类的构造次序:深入理解对象构建流程

在上一章“C++ 基础继承”中,你已经了解到类可以从其他类继承成员与函数。本节将进一步探讨当实例化一个派生类时,其构造过程的精确次序。

首先,引入几个新类以作说明:

class Base
{
public:
    int m_id{};

    Base(int id = 0)
        : m_id{ id }
    {
    }

    int getId() const { return m_id; }
};

class Derived : public Base
{
public:
    double m_cost{};

    Derived(double cost = 0.0)
        : m_cost{ cost }
    {
    }

    double getCost() const { return m_cost; }
};

在此例中,DerivedBase 派生。

Code::Blocks安装

由于 Derived 继承了 Base 的函数与变量,你可能误以为 Base 的成员被复制进了 Derived。事实并非如此。更准确的看法是:把 Derived 视为一个由两部分组成的类——一部分是 Derived 自身,另一部分则是 Base

Code::Blocks安装

你已多次见到普通(非派生)类的实例化过程:

int main()
{
    Base base;

    return 0;
}

Base 不继承任何类,因此是非派生类。C++ 先为 Base 分配内存,再调用 Base 的默认构造函数完成初始化。

现在观察派生类的实例化:

int main()
{
    Derived derived;

    return 0;
}

亲自尝试时,你会感觉与实例化非派生类 Base 无异;然而背后过程略有不同。如前所述,Derived 由两部分组成:一个 Base 部分和一个 Derived 部分。当 C++ 构造派生对象时,它按阶段进行:

  1. 首先构造最基类(继承树顶端);
  2. 然后按顺序逐级构造每个子类,直至最派生类(继承树底端)最后被构造。

因此,当我们实例化 Derived 时,先构造 Derived 中的 Base 部分(使用 Base 的默认构造函数);待 Base 部分完成后,再构造 Derived 部分(使用 Derived 的默认构造函数)。至此已无更派生之类,构造结束。

这一过程极易通过示例展示:

#include <iostream>

class Base
{
public:
    int m_id{};

    Base(int id = 0)
        : m_id{ id }
    {
        std::cout << "Base\n";
    }

    int getId() const { return m_id; }
};

class Derived : public Base
{
public:
    double m_cost{};

    Derived(double cost = 0.0)
        : m_cost{ cost }
    {
        std::cout << "Derived\n";
    }

    double getCost() const { return m_cost; }
};

int main()
{
    std::cout << "Instantiating Base\n";
    Base base;

    std::cout << "Instantiating Derived\n";
    Derived derived;

    return 0;
}

程序输出:

Instantiating Base
Base
Instantiating Derived
Base
Derived

可见,构造 Derived 时,Base 部分首先被构造。这符合逻辑:子对象不能脱离父对象而存在。同时也是安全之举:子类常需使用父类的变量与函数,而父类对子类一无所知;先实例化父类可确保这些变量在派生类创建并准备使用它们时已被初始化。

继承链的构造次序

有时类的继承层次更深:类继承自其他类,而这些类又继承自更上层的类。例如:

#include <iostream>

class A
{
public:
    A()
    {
        std::cout << "A\n";
    }
};

class B : public A
{
public:
    B()
    {
        std::cout << "B\n";
    }
};

class C : public B
{
public:
    C()
    {
        std::cout << "C\n";
    }
};

class D : public C
{
public:
    D()
    {
        std::cout << "D\n";
    }
};

请记住,C++ 总是先构造“第一”或“最基类”,然后沿继承树逐层向下,依次构造每个派生类。

下列小程序展示了沿整条继承链的创建顺序:

int main()
{
    std::cout << "Constructing A: \n";
    A a;

    std::cout << "Constructing B: \n";
    B b;

    std::cout << "Constructing C: \n";
    C c;

    std::cout << "Constructing D: \n";
    D d;
}

输出:

Constructing A:
A
Constructing B:
A
B
Constructing C:
A
B
C
Constructing D:
A
B
C
D

结论

C++ 以阶段方式构造派生类:从继承树顶端的最基类开始,直至底端的最派生类结束。每构造一个类,便调用该类对应的构造函数,以初始化该部分对象。

本节示例均为简化起见,均使用基类默认构造函数。下一章将深入探讨派生类构造过程中构造函数的角色,包括如何显式指定派生类欲使用的基类构造函数。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"