-
2020-12-15 09:50:38
背景是:菱形继承
比如一个类C通过继承类A和类B,但是类A和类B又同时继承于公共基类N。示意图如下:
这种继承方式也存在数据的二义性,这里的二义性是由于他们间接都有相同的基类导致的。 这种菱形继承除了带来二义性之外,还会浪费内存空间。
类C中存在 两份的基类N,分别存在类A和类B中,如果数据多则严重浪费空间,也不利于维护, 我们引用基类N中的数据还需要通过域运算符进行区分。例如:
C data;
data.A::m_data1 = 10;
data.B::m_data1 = 10;
解决办法:使用虚继承
使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 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,直接访问就不会再有歧义了。不然就会有2分成员变量m_a.
更多相关内容 -
C++ 虚继承对基类构造函数调用顺序的影响
2021-01-20 03:48:10继承作为面向对象编程的一种基本特征,其使用频率... 假设derived 虚继承自base类,那么derivd与base是一种“has a”的关系,即derived类有一个指向base类的vptr。(貌似有些牵强!某些编译器确实如此) 因此虚 -
C++虚继承之类的实际大小
2020-12-22 16:53:50这几天翻箱底将去年买的《深度探索C++对象模型》这本NB的书拿出来看看,The Semantics of Data这一章中发现了一个过去一直没有想到的一个问题,问题如下 输出下面class的大小: class X{}; class Y : public ... -
C++虚继承详解
2022-04-21 15:57:14C++虚继承详解 看侯捷老师的C++内存模型时,讲到了虚继承。虚继承算是C++特有的知识了,特此记录下。 什么是虚继承 由于C++支持多继承,可能会出现菱形继承,代码如下: #include <iostream> // std::cout ...C++虚继承详解
看侯捷老师的C++内存模型时,讲到了虚继承。虚继承算是C++特有的知识了,特此记录下。
什么是虚继承
由于C++支持多继承,可能会出现菱形继承,代码如下:
#include <iostream> // std::cout std::endl class base { public: long long par=0; void show(int par) const noexcept; }; void base::show(int par) const noexcept { par=par; std::cout << "par:" << par << std::endl; } class derived1 : public base{}; class derived2 : public base{}; class final_derived : public derived1, public derived2 { public: long long fpar=3; }; int main(void) { final_derived object; // 两个show()函数,编译器不知道调用哪个 object.show(); return 0; }
首先需要明白,编译器寻找普通成员函数时,和this指针有关系:
this指针是一个指针常量,其地址不能改变,指向当前对象,做为成员函数的第一个默认缺省参数,由编译器管理。
this指针的两个作用:
其一,类似于模板函数的类型推导确定对象所属类型,所以不同的类调用同名函数,是不会出现问题的,并确定函数操作的数据块大小;其二,它的值就是对象object的地址;因此,通过this指针,当存在多个同名函数时,编译可以根据对象推导参数类型,找到这个类对应的函数,并操作对应空间的数据。
接下里,剖析下多继承中菱形继承问题。
多继承–成员函数方面
继承关系中的成员函数
由于函数会占用内存中代码区的资源,所以如果子类不用修改父类中的某一个成员函数,那直接用父类的这个函数就好了:
#include <iostream> using namespace std; class base { public: long long par=0; void show(int par) const noexcept; }; void base::show(int par) const noexcept { par=par; std::cout << "par:" << par << std::endl; } class derived1 : public base{ public: void show(int par){ cout<<"show of derived1"<<endl; } }; class derived2 : public base{}; class final_derived : public derived2,public derived1 { public: long long fpar=3; }; // 成员函数地址读取 template<typename dst_type,typename src_type> dst_type pointer_cast(src_type src) { // 巧妙地转换:由于static_cast不能转换两个毫不相关的变量,利用void* 进行转换 return *static_cast<dst_type*>(static_cast<void*>(&src)); } int main(void) { base* p1 = pointer_cast<base*>(&base::show); derived1* p2 = pointer_cast<derived1*>(&derived1::show); derived2* p3 = pointer_cast<derived2*>(&derived2::show); cout<<p1<<endl<<p2<<endl<<p3<<endl; return 0; }
三个成员函数的地址为:0x401550 0x4159c0 0x401550
可以看出:由于derived2的show函数就是用的base父类的show函数,而没有新建show函数;说的直白点,就是derived1的成员函数show实际变成了show(derived* const this,int par),而derived2的成员函数show还是show(base* const this,int par)。
多继承中成员函数问题
接下来,并在main函数加入如下代码:
final_derived object; object.show(1);
发现编译器直接报错:show函数目标不明确
一开始我是这么以为的:
这是由于final_derived没有重写show函数,所以会调用父类的show函数。然而,final_derived类调用show函数时,既能匹配show(derived* const this,int par),也能匹配show(base* const this,int par),编译器不明确到底调用哪个。
然而,实事并不是这样,我把derived1中的show函数删除了,也就是只剩一个show(base* const this,int par)了,但仍然出现show函数目标不明确的报错。所以,实事就是编译器在进行语法分析时,发现final_derived有两个相同的show函数,直接就报错了。
解决方法
为避免调用函数时,语法问题造成调用失败,有三种方法可以解决:
一、final_derived重写show函数:
class final_derived : public derived2,public derived1 { public: long long fpar=3; void show(int par){ derived1::show(par); } };
但这样有个缺点,本来final_derived就是用的derived1的方法,且未作任何修改,按C++的设计思想,直接用父类derived1类的show方法就可以了,不应该用额外的内存。
二、调用的时候,指定具体的类,给this指针传更精确的类型:
int main(void) { final_derived object; object.derived1::show(1); object.derived2::show(2); return 0; }
这样就是写代码会很麻烦,别人还得知道你是怎么继承的。
三、虚继承
也就是在derived1类和derived2类继承base时,添加virtual关键字:
#include <iostream> using namespace std; class base { public: long long par=0; void show(int par) const noexcept; }; void base::show(int par) const noexcept { par=par; std::cout << "par:" << par << std::endl; } class derived1 : virtual public base{ public: void show(int par){ std::cout << "show of derived1"<< std::endl; } }; class derived2 : virtual public base{}; class final_derived : public derived2,public derived1 { public: long long fpar=3; }; int main(void) { final_derived object; object.show(1); return 0; }
这里输出的是show of derived1。
如果删除derived1的show函数,输出为par=1,也就是调用base的show函数虚继承实现原理
derived1 和 derived1 虚继承 base,会新建一个虚基类表,存储虚基类相对直接继承类的偏移量,并把指向虚基类表的虚基类指针存入类中。
这样,final_derived在调用show时,过程如下:
- 首先找自己类中有没有show函数;如果没有,找父类。
- 父类中如果能找到,就用父类的show函数。(注意:如果derived1和derived2都重写了show函数,object.show(1)仍然报目标不明确的错误)
- 找derived1和derived2和虚基类表,如果发现show函数在两个类中的虚基类表中都存在,就直接调用base的show函数。
可以看出,虚继承并不能保证object.show(1)的合法调用,最好不要用多继承,就把虚继承这种机制当作语法糖吧。
多继承–成员变量方面
继承中类成员变量的分布
删除上述代码的show函数,专注于成员变量par上,观察object对象中的成员变量分布,以及它的大小。
#include <iostream> using namespace std; class base { public: long long par=0; void show(int par) const noexcept; }; void base::show(int par) const noexcept { par=par; std::cout << "par:" << par << std::endl; } class derived1 : public base{}; class derived2 : public base{}; class final_derived : public derived2,public derived1 { public: long long fpar=3; }; int main(void) { final_derived object; cout<<sizeof(object)<<endl; return 0; }
其大小为24bytes(64位机器下),成员变量分布为:
依次是从derived2类继承的par,从derived1类继承的par,以及自身的fpra。由于final_derived是先继承的 derived2 后继承 derived1,因此从derived2继承来的par也分布在前端。那么现在问题来了:其一:这个类中有两个par变量,要怎么访问呢?其二:从上述代码来看,final_derived直接用base的par就可以了,用两个par变量不是浪费空间吗?
解决办法
如果只解决第一个问题,可以通过指明具体类的方法:
cout<<object.derived1::par<<endl;
但如果还要解决第二个问题,仍然得借助虚继承,为了更方便说明,我加了两个参数par2和par3:
#include <iostream> using namespace std; class base { public: long long par=0; long long par2=1; long long par3=2; void show(int par) const noexcept; }; void base::show(int par) const noexcept { par=par; std::cout << "par:" << par << std::endl; } class derived1 : virtual public base{}; class derived2 : virtual public base{}; class final_derived : public derived2,public derived1 { public: long long fpar=3; }; int main(void) { final_derived object; cout<<sizeof(object)<<endl; return 0; }
输出结果为48bytes,内存分布从上到下依次为:derived2的虚基类指针,derived1的虚基类指针,final_derived自身的fpar,base的三个成员变量(单继承中,父类的成员变量是放前面的)。
访问成员变量类似于成员函数的调用,先看类本身是否存在这个变量,然后去父类中找,最后找父类的虚继承表。
题外话: 虽然我一直觉得组合比继承好,但这里用组合好像没啥办法省内存,算是继承的一个优点吧,但代价就是代码写起来很麻烦。
-
C++ 虚继承
2020-06-13 20:21:09虚继承是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。 ...前言
虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。
虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要,因为这样只会降低效率和占用更多的空间。
例如菱形继承:
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。
注意:
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生
virtual修饰继承方式:
1 //继承的动作 虚继承 2 //父类:虚基类 3 class 子类:virtual public 父类 4 { 5 6 };
vbptr(虚基类指针) 其中v是virtual 虚 b是base 基类 prt指针(vbptr指向虚基类表)vbtable(虚基类表 ) 保存了当前的虚指针相对于虚基类的首地址的偏移量总结:之所以 产生 vbptr和vbtable 目的 保证 不管多少个继承 虚基类的数据只有一份。 -
C++ 虚继承详解
2021-02-08 13:51:24多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一...多继承(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; }
虚继承(Virtual Inheritance)
为了解决多继承时的命名冲突和冗余数据问题,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 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
现在让我们重新梳理一下本例的继承关系,如下图所示:
观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
在实际开发中,位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下,使用虚继承的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是虚基类,况且新类的开发者也无法改变已经存在的类体系。
C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。
虚基类成员的可见性
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。以图2中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,C++ 之后的很多面向对象的编程语言,例如 Java、C#、PHP 等,都不支持多继承。
-
一文读懂C++虚继承的内存模型
2021-06-30 21:04:01一文读懂C++虚继承的内存模型1、前言2、多继承存在的问题3、虚继承简介4、虚继承在标准库中的使用5、虚继承下派生类的内存布局解析6、总结 1、前言 C++虚继承的内存模型是一个经典的问题,其具体实现依赖于编译器,... -
C++虚继承与虚函数
2017-10-03 21:11:34虚继承在菱形继承中出现的数据二义性问题,使得数据访问时变得复杂,并且导致了数据冗存。虚继承则解决了从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题。关键字:virtual用法:将共同... -
C++虚继承内存对象模型探讨.pdf
2020-04-16 18:29:03C++ 虚继承内存对象模型探讨 最近看了下 Inside C++ 里面讲的对虚继承层次的对象的内存布局 发现在不同编 译器实现有所区别因此自己动手探索了一下结果如下 首先说说 GCC 的编译器 . 它实现比较简单不管是否虚继承 ... -
C++ 虚继承实现原理(虚基类表指针与虚基类表)
2018-06-03 18:45:20虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的... -
C++虚继承实现原理——解决菱形继承问题
2018-11-11 14:36:14首先给出以下继承关系,以便描述虚继承原理: class AAA { public: int age; }; class BBB:virtual public AAA//变为虚继承,A变为虚基类 { }; class CCC:virtual public AAA { }; class DDD:public BBB, ... -
C++中虚继承
2021-01-06 09:25:14一、虚继承和虚基类 1、多继承产生的冲突 在C++中多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示... -
C++虚继承的意义和使用
2017-10-15 11:05:19虚继承:多个派生类保存相同基类的同名成员时,虽可以在不同的数据成员中分别存放不同的数据 ,但我们只需要相同的一份。 解决了多父类重复成员只保留一份的问题。 比如现在有一个沙发床,它既有床的属性又有... -
C++ 虚继承的对象模型
2016-06-17 17:24:07我们知道,虚继承的基类在类的层次结构中只可能出现一个实例。虚基类在类的层次结构中的位置是不能固定的,因为继承了虚基类的类可能会再次被其他类多继承。 比如class A: virtual T{} 这时T的位置如果相对于A是... -
C/C++:从内存布局理解C++虚继承的实现原理
2018-09-13 21:08:29Visual Studio提供给用户显示C++对象在内存中的布局的选项: /d1reportSingleClassLayoutXXX // XXX替换为类的名字 /d1reportAllClassLayout 创建工程取名为research,在research中找到research源文件 右键... -
C++虚继承的实现方式与内存布局
2014-11-13 17:58:18虚基类表中存放了虚基类的成员在派生类内存空间中的偏移量...... -
C++虚继承时的构造函数
2016-12-12 21:14:44在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造... -
C++虚继承与普通继承的区别
2015-04-16 15:54:17虚继承的时候在子类的对象中会多出一个叫虚类指针的大小,有的资料说这个指针指向的内存里面包含了该子类的偏移量和到基类的距离。但是我跟踪过这段内存,发现里面的数据没有规律,也找不到更多的支撑材料,权且先... -
C++多重继承与虚继承分析
2021-01-01 12:44:23本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下: 一、多重继承 我们知道,在单继承中,派生类的对象中包含了基类部分 和 派生类... -
C++普通继承和虚继承详解
2020-06-07 22:18:46继承 继承概念 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类。 继承特点 子类拥有父类的所有属性和方法(除了构造... -
C++虚继承(九) --- 构造函数调用顺序的实用之处
2016-03-18 17:46:04笔者最近学习过程中发现对C++的虚拟继承不是很明朗,故在这里对虚继承做个小结。 首先说下遇到的问题吧。代码如下(代码来自于何海涛《程序员面试精选100题第32题)。意图是要设计一个不能被继承的类,类似java中的... -
c++ 虚继承与继承的差异
2017-10-26 14:33:30虚继承对基类构造函数调用顺序的影响。经过仔细推敲,发现没有彻底说清楚虚继承与普通继承之间的关系。所以用下面的文字再说明一下。 首先,重复一下虚拟继承与普通继承的区别有: 假设derived 继承自base类... -
C++虚继承下的内存模型
2019-05-27 16:15:25对于普通继承,基类子对象始终位于派生类对象的前面(也即基类成员变量始终在派生类成员变量的前面),而且不管继承层次有多深,它相对于派生类对象顶部的偏移量是固定的。请看下面的例子: obj_a、obj_b、... -
关于C++中菱形继承和虚继承的问题总结
2021-01-01 14:36:29本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧。 继承: 1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承 2. 多继承–一... -
c++虚继承与继承的区别
2015-03-29 20:10:36下面是我写的一个菱形集成的利用虚继承里解决二义性的例子 虚继承和继承的区别在于: 继承是is的关系,是说子类是属于父类的。 而虚继承是has的关系,是子类有一个指针指向了父类。 这两者的区别在内存的角度... -
C++中虚继承产生的虚基类指针和虚基类表,虚函数产生的虚函数指针和虚函数表
2021-10-19 22:50:16一、虚继承产生的虚基类表指针和虚基类表 如下代码:写一个棱形继承,父类Base,子类Son1和Son2虚继承Base,又来一个类Grandson继承Son1和Son2。 代码: class Base { public: int a; protected: int b; private: ... -
C++ 虚继承(虚基类表指针与虚基类表)
2019-01-21 21:07:20在C++中,我们会遇到virtual这个关键字,但是它有两种含义:虚函数和虚继承,它们两个是完全无相关的两个概念。 什么是虚继承 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类... -
C++虚继承内存分布
2019-06-01 13:57:20本周抽空看虚继承的时候,研究了下虚继承的对象的内存分布,C++虚继承主要解决了菱形继承访问不明确的问题。 上述继承关系,定义DD对象的构造函数输出如下:AA BB AA CC DD,对象中存在两份AA。若AA存在成员变量,... -
c++之虚继承(多重继承的问题)
2020-06-13 13:56:11导引:多重继承和多继承 什么是多重继承:如图 什么是多继承:如图 继承中的特殊结构 菱形继承结构带来的问题,D会有两个A中的数据成员 class A { public: int a; }; class B:public A { public: int b; }; ...