MST

星途 面试题库

面试题:C++虚拟继承在解决多重继承二义性中的深度剖析

在C++中,虚拟继承是解决多重继承二义性的重要手段。请详细说明虚拟继承的原理,包括虚拟基类的存储方式、构造函数的调用顺序,以及在复杂继承体系(例如菱形继承)中如何通过虚拟继承避免数据冗余和二义性,给出完整且复杂的代码示例并分析。
25.9万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

虚拟继承原理

  1. 虚拟基类存储方式:虚拟继承中,对象会额外存储一个指针(通常称为虚基表指针),该指针指向一个虚基表。虚基表记录了虚拟基类相对于派生类对象起始地址的偏移量。这样,通过这个偏移量,在需要访问虚拟基类成员时,就可以正确定位到虚拟基类的位置,从而避免了数据冗余。
  2. 构造函数调用顺序:在虚拟继承体系中,构造函数调用顺序为:
    • 首先调用虚拟基类的构造函数,且在整个继承体系中,无论虚拟基类被继承多少次,都只调用一次。
    • 然后按照继承列表中声明的顺序调用非虚拟基类的构造函数。
    • 最后调用派生类自身的构造函数。

在菱形继承中避免数据冗余和二义性

考虑以下菱形继承结构:

class A {
public:
    int data;
    A(int value) : data(value) {}
};

class B : virtual public A {
public:
    B(int value) : A(value) {}
};

class C : virtual public A {
public:
    C(int value) : A(value) {}
};

class D : public B, public C {
public:
    D(int value) : A(value), B(value), C(value) {}
};

代码分析

  1. 数据冗余:在上述代码中,如果不使用虚拟继承,D对象中会包含两份A类的数据成员data,造成数据冗余。而使用虚拟继承后,D对象中只包含一份A类的数据成员data,避免了数据冗余。
  2. 二义性:如果不使用虚拟继承,当D类对象访问A类成员时,会出现二义性,因为编译器不知道应该访问从B继承过来的A还是从C继承过来的A。而通过虚拟继承,无论从B还是C继承,A类只有一份实例,从而避免了二义性。例如:
#include <iostream>

int main() {
    D obj(10);
    std::cout << obj.data << std::endl; // 不会出现二义性
    return 0;
}

在这个例子中,D类对象obj可以直接访问data成员,不会产生二义性,并且数据也不会冗余。