0
点赞
收藏
分享

微信扫一扫

虚基类和虚拟继承一点通


虚基类和虚拟继承

  • ​​多重继承​​
  • ​​虚继承​​
  • ​​内存分布​​

多重继承

多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示

虚基类和虚拟继承一点通_派生类


类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。

在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径。下面是菱形继承的具体实现:

//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}

这段代码实现了上图所示的菱形继承,第 25 行代码试图直接访问成员变量 m_a,结果发生了错误,因为类 B 和类 C 中都有成员变量 m_a(从 A 类继承而来),编译器不知道选用哪一个,所以产生了歧义。

为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:

void seta(int a){ B::m_a = a; }

这样表示使用 B 类的 m_a。当然也可以使用 C 类的:

void seta(int a){ C::m_a = a; }

虚继承

为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。

在继承方式前面加上 virtual 关键字就是虚继承,请看下面的例子:

//间接基类A
class A{
protected:
int m_a;
};

//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};

//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};

//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};

int main(){
D d;
return 0;
}

这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

**虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。**其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

现在让我们重新梳理一下本例的继承关系,如下图所示:

虚基类和虚拟继承一点通_成员变量_02


观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。

换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。

内存分布

#include <iostream>
using std::cout;
using std::endl;
class Person {
public:
virtual void show() {}
};
class Person_ordinary:public Person {
public:
int b;
virtual void show() {}
void print() {}
};
class Person_A: public virtual Person {
public:
virtual void show() {}
};
class Person_B: public virtual Person {
public:
virtual void show() {}
};
class Person_C: public Person_A, Person_B{
public:
virtual void show() {}
};
int main() {
cout << sizeof(Person) << endl; // 一个vptr占8个字节
cout << sizeof(Person_ordinary) << endl; // 8字节对齐原则,加上一个vptr占8个字节,所以占16个字节
cout << sizeof(Person_A) << endl; // 一个vptr占8个字节
cout << sizeof(Person_B) << endl; // 一个vptr占8个字节
cout << sizeof(Person_C) << endl; // 一个vptr占8个字节,加上一个指向基类的副本8字节
return 0;
}


举报

相关推荐

0 条评论