C++中虚继承时的构造函数

简介: 在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。下面我们以菱形继承为例来演示构造函数的调用: #include <iostream> using namespace std; //虚基类A class A{ public: A(int a); protected: int m_a; }; A:

在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

网络异常,图片无法展示
|

下面我们以菱形继承为例来演示构造函数的调用:

#include <iostream>
    using namespace std;
    //虚基类A
    class A{
    public:
        A(int a);
    protected:
        int m_a;
    };
    A::A(int a): m_a(a){ }
    //直接派生类B
    class B: virtual public A{
    public:
        B(int a, int b);
    public:
        void display();
    protected:
        int m_b;
    };
    B::B(int a, int b): A(a), m_b(b){ }
    void B::display(){
        cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
    }
    //直接派生类C
    class C: virtual public A{
    public:
        C(int a, int c);
    public:
        void display();
    protected:
        int m_c;
    };
    C::C(int a, int c): A(a), m_c(c){ }
    void C::display(){
        cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
    }
    //间接派生类D
    class D: public B, public C{
    public:
        D(int a, int b, int c, int d);
    public:
        void display();
    private:
        int m_d;
    };
    D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
    void D::display(){
        cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
    }
    int main(){
        B b(10, 20);
        b.display();
        C c(30, 40);
        c.display();
        D d(50, 60, 70, 80);
        d.display();
        return 0;
    }

运行结果:

m_a=10, m_b=20

m_a=30, m_c=40

m_a=50, m_b=60, m_c=70, m_d=80

请注意第 50 行代码,在最终派生类 D 的构造函数中,除了调用 B 和 C 的构造函数,还调用了 A 的构造函数,这说明 D 不但要负责初始化直接基类 B 和 C,还要负责初始化间接基类 A。

而在以往的普通继承中,派生类的构造函数只负责初始化它的直接基类,再由直接基类的构造函数初始化间接基类,用户尝试调用间接基类的构造函数将导致错误。现在采用了虚继承,虚基类 A 在最终派生类 D 中只保留了一份成员变量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在调用 A 的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化 m_a。

为了避免出现这种矛盾的情况,C++ 干脆规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。在第 50 行代码中,调用 B 的构造函数时试图将 m_a 初始化为 90,调用 C 的构造函数时试图将 m_a 初始化为 100,但是输出结果有力地证明了这些都是无效的,m_a 最终被初始化为 50,这正是在 D 中直接调用 A 的构造函数的结果。另外需要关注的是构造函数的执行顺序。

虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。修改本例中第 50 行代码,改变构造函数出现的顺序:

D::D(int a, int b, int c, int d): B(90, b), C(100, c), A(a), m_d(d){ }

虽然我们将 A() 放在了最后,但是编译器仍然会先调用 A(),然后再调用 B()、C(),因为 A() 是虚基类的构造函数,比其他构造函数优先级高。如果没有使用虚继承的话,那么编译器将按照出现的顺序依次调用 B()、C()、A()。

相关文章
|
1天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
41 30
|
1月前
|
编译器 C++
C++的基类和派生类构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。 这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数。 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
23 1
|
2月前
|
Java 编译器 程序员
C++中的语法知识虚继承和虚基类
**C++中的多继承可能导致命名冲突和数据冗余,尤其在菱形继承中。为解决这一问题,C++引入了虚继承(virtual inheritance),确保派生类只保留虚基类的一份实例,消除二义性。虚继承通过`virtual`关键字指定,允许明确访问特定路径上的成员,如`B::m_a`或`C::m_a`。这样,即使基类在继承链中多次出现,也只有一份成员副本,简化了内存布局并避免冲突。虚继承应在需要时提前在继承声明中指定,影响到从虚基类派生的所有后代类。**
51 7
|
3月前
|
安全 编译器 C++
C++一分钟之-构造函数与析构函数
【6月更文挑战第20天】C++中的构造函数初始化对象,析构函数负责资源清理。构造函数有默认、参数化和拷贝形式,需注意异常安全和成员初始化。析构确保资源释放,避免内存泄漏,要防止重复析构。示例代码展示了不同构造函数和析构函数的调用情况。掌握构造和析构是有效管理对象生命周期和资源的关键。
37 2
|
2月前
|
编译器 C++
【C++】详解构造函数
【C++】详解构造函数
|
4月前
|
C++ Linux
|
3月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
3月前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。
|
3月前
|
编译器 C语言 C++
【C++】:构造函数和析构函数
【C++】:构造函数和析构函数
36 0
|
4月前
|
编译器 C++
C++的基类和派生类构造函数
在 C++ 中,类的构造函数不能被继承,但基类的普通成员函数可以在派生类中访问。派生类必须通过其构造函数初始化继承的成员变量,由于私有成员变量无法直接初始化,因此需要在派生类构造函数中调用基类的构造函数来完成。示例代码显示了如何在派生类构造函数中调用基类构造函数,确保正确初始化。构造函数的调用顺序遵循自顶向下、从基类到派生类的规则,且只能调用直接基类的构造函数。如果基类没有默认构造函数,而派生类未指定构造函数调用,会导致编译错误。
41 4