-
2021-12-18 21:38:39
#include <iostream> using namespace std; //虚基类 class Animal { public: int age; protected: private: }; //绵羊类 //class Sheep : public Animal {}; class Sheep : virtual public Animal {}; //骆驼类 //class Camel : public Animal {}; class Camel : virtual public Animal {}; //羊驼 继承绵羊和骆驼 class Alpaca : public Sheep, public Camel {}; void test01() { Alpaca alpaca; alpaca.Sheep::age = 10; alpaca.Camel::age = 20; //不明确,造成age二义性,不知道该访问哪一个 //内存空间浪费,继承了一些重复的成员属性 //cout << alpaca.age << endl; cout << alpaca.Sheep::age << endl; } void test02() { //采用虚继承: //当发生虚继承后,Sheep和Camel类中会继承vbptr指针(虚基类指针)指向一个虚基表vbtable //虚基表中记录了偏移量,通过偏移量可以找到唯一的一个age(成员属性) Alpaca alpaca; alpaca.Sheep::age = 10; alpaca.Camel::age = 20; cout << alpaca.Sheep::age << endl; cout << alpaca.Camel::age << endl; cout << alpaca.age << endl; } void test03() { Alpaca alpaca01; cout << *((int*) * (int*)& alpaca01 + 1) << endl; //&alpaca01 去首地址 //(int*)&alpaca01 转到虚基表 //*(int*)&alpaca01 解引用,找到第一个偏移量,但是这个偏移量不是alpaca01的 //((int*)*(int*)&alpaca01+1) 转为整型指针,指向下一个偏移量,这个偏移量才是alpaca01的 //*((int*)*(int*)&alpaca01+1) 解引用,即可得到偏移量 cout << *((int*) * ((int*)& alpaca01 + 1) + 1) << endl; cout << "age = " << ((Animal*)((char*)& alpaca01 + *((int*) * (int*)& alpaca01 + 1)))->age << endl; //(char*)& alpaca01 //首地址转为char型 //(char*)& alpaca01 + *((int*) * (int*)& alpaca01 + 1)) //加上偏移量 此时地址指向age所在的空间,但此时指针为char类型 //((Animal*)((char*)& alpaca01 + *((int*) * (int*)& alpaca01 + 1))) 转为类指针 通过类指针访问age cout << "age = " << *((int*)((char*)& alpaca01 + *((int*) * (int*)& alpaca01 + 1))) << endl; } int main() { //test01(); //test02(); test03(); return 0; }
输出结果:
更多相关内容 -
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++ 虚继承详解
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++基础]虚继承实现原理
2019-04-12 12:54:00在C++中,我们会遇到virtual这个关键字,但是它有两种含义:虚函数和虚继承,它们两个是完全无相关的两个概念。 什么是虚继承 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中...在C++中,我们会遇到virtual这个关键字,但是它有两种含义:虚函数和虚继承,它们两个是完全无相关的两个概念。
什么是虚继承
虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的具体方式是,将基类指针指向继承类(继承类有基类的拷贝)中的基类对象的地址,但是多重继承可能存在一个基类的多份拷贝,这就出现了二义性。
当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份。例如:
class CBase { }; class CDerive1:virtual public CBase{ }; class CDerive2:virtual public CBase{ }; class CDerive12:public CDerive1,CDerive2{ };
则在类CDerive12的对象中,仅有类CBase的一个对象数据
虚继承实现原理
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(任何类型的指针变量都是占用4个字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
实际上,vbptr指的是虚基类表指针,该指针指向了一个虚基类表,虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。虚继承与虚函数的异同
相似之处:都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
不同之处:虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。#include<iostream> using namespace std; class A //大小为4 { public: int a; }; class B :virtual public A //大小为12,变量a,b共8字节,虚基类表指针4 { public: int b; }; class C :virtual public A //与B一样12 { public: int c; }; class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针 { public: int d; }; int main() { A a; B b; C c; D d; cout << sizeof(a) << endl; cout << sizeof(b) << endl; cout << sizeof(c) << endl; cout << sizeof(d) << endl; system("pause"); return 0; } // //1 > class A size(4) : //1 > +-- - //1 > 0 | a //1 > +-- - //1 > //1 > class B size(12) : //1 > +-- - //1 > 0 | {vbptr} //1 > 4 | b //1 > +-- - //1 > +-- - (virtual base A) //1 > 8 | a //1 > +-- - //1 > //1 > B::$vbtable@: //1 > 0 | 0 //1 > 1 | 8 (Bd(B + 0)A) //1 > vbi:class offset o.vbptr o.vbte fVtorDisp //1 > A 8 0 4 0 //1 > //1 > class C size(12) : //1 > +-- - //1 > 0 | {vbptr} //1 > 4 | c //1 > +-- - //1 > +-- - (virtual base A) //1 > 8 | a //1 > +-- - //1 > //1 > C::$vbtable@: //1 > 0 | 0 //1 > 1 | 8 (Cd(C + 0)A) //1 > vbi:class offset o.vbptr o.vbte fVtorDisp //1 > A 8 0 4 0 //1 > //1 > class D size(24) : //1 > +-- - //1 > 0 | +-- - (base class B) //1 > 0 | | {vbptr} //1 > 4 | | b //1 > | +-- - //1 > 8 | +-- - (base class C) //1 > 8 | | {vbptr} //1 > 12 | | c //1 > | +-- - //1 > 16 | d //1 > +-- - //1 > +-- - (virtual base A) //1 > 20 | a //1 > +-- - //1 > //1 > D::$vbtable@B@: //1 > 0 | 0 //1 > 1 | 20 (Dd(B + 0)A) //1 > //1 > D::$vbtable@C@: //1 > 0 | 0 //1 > 1 | 12 (Dd(C + 0)A) //1 > vbi:class offset o.vbptr o.vbte fVtorDisp //1 > A 20 0 4 0 //1 >
-
C++中虚继承的作用及底层实现原理
2017-08-30 22:26:11虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的... -
C++ 虚继承实现原理(虚基类表指针与虚基类表)
2019-03-18 16:13:39虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:其一,浪费存储空间;第二,存在二义性问题,通常可以将派生类对象的地址赋值给基类对象,实现的... -
【C++11】 从内存布局看C++虚继承的实现原理
2019-05-08 20:06:55准备工作 1、VS2012使用命令行选项查看对象的...微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout。使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命... -
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-10-31 21:21:34因为基类指针不能访问派生类的普通成员函数,但是可以访问派生类的虚函数,所以虚函数是实现C++多态性的关键。 派生类继承了基类的虚函数并重新定义(重写、覆盖)虚函数,通过基类指针去指向其派生类对象,进行... -
C/C++:从内存布局理解C++虚继承的实现原理
2018-09-13 21:08:29Visual Studio提供给用户显示C++对象在内存中的布局的选项: /d1reportSingleClassLayoutXXX // XXX替换为类的名字 /d1reportAllClassLayout 创建工程取名为research,在research中找到research源文件 右键... -
c++虚继承
2021-06-01 16:29:32看完C++中虚继承的作用及底层实现原理,文中说虚继承是通过虚基类指针实现的,此处验证一下其成员分布。 #include <iostream> using namespace std; class A { public: virtual void fun() {}; int a; }; ... -
C++虚继承中的虚基类表
2021-07-19 02:03:28在之前的 C++ 继承中已经说过虚继承基本概念,这里不再赘述。这篇文章主要探究虚继承的原理。文章中多处给出了类实例对象的内存布局,查看其内存布局时,使用 VS 工具 /d1 reportAllClassLayout 进行查看,关于这个... -
详解C++中虚析构函数的作用及其原理分析
2021-01-01 06:50:36C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数的作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类... -
C++中多态实现的原理分析、虚继承的原理
2018-05-12 21:43:35多态的原理分析 在面向对象的语言中,封装、继承、多态三大特性。我们今天说说C++中多态的实现原理。 多态往往是用来在继承中,子类中的某些行为与父类中的不同,但是为了降低...1)必须在继承中,存在虚函数,... -
C++虚函数实现原理
2015-10-24 21:11:09虚函数表中虚函数的分布情况;其中包括发生继承的情况下虚函数表中虚函数的分布情况; -
c++ 虚函数实现原理
2022-03-07 09:43:28对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 虚函数表指针(vptr)创建时机 vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建... -
C++ 虚继承内存对象模型
2020-01-09 11:08:38文章目录C++ 虚继承内存对象模型1. 普通虚继承1.1 内存大小1.2 内存布局1.3 指针赋值1.4 基类指针调用1.5 子类指针的调用2. 子类添加自己的虚函数2.1 虚函数表2.2 虚继承表2.3 内存布局3. 菱形继承4. 总结 C++ 虚... -
C++普通继承和虚继承详解
2020-06-07 22:18:46继承 继承概念 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类。 继承特点 子类拥有父类的所有属性和方法(除了构造... -
一文读懂C++虚继承的内存模型
2021-06-30 21:04:01C++虚继承的内存模型是一个经典的问题,其具体实现依赖于编译器,可能会出现较大差异,但原理和最终的目的是大体相同的。本文将对g++中虚继承的内存模型进行详细解析。 2、多继承存在的问题 C++的多继承是指从多个... -
C++ 虚函数和虚继承解析
2018-08-09 21:53:58本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有不对的地方请指出。下面都是以VC2008编译器对这两种机制内部实现为例。 有喜欢或者想学习C/C++的朋友加一下我的C/C++交流群815393895。谢谢大家的支持... -
虚继承实现原理
2021-04-24 18:12:37虚继承的作用 两个父类中有祖类中的数据,然后子类会继承两个父类的数据,会产生二义性问题 class Animal { public: Animal() { mA = 100; } public: int mA; }; class Sheep :virtual public Animal{ }; ... -
c++继承之虚继承
2018-08-05 17:03:02虚继承 存在的理由 存在即合理 虽然我们相信这句话,但是为什么合理呢?是什么促使它存在的呢? 我们引出一个概念,菱形继承 什么是菱形继承: class A //大小为4 { public: int a; }; class B : ... -
浅谈C++多态实现原理(虚继承的奥秘)
2018-12-24 17:47:24根据我的已有知识,如果要实现C++的多态,那么,基类中相应的函数必须被声明为虚函数(或纯虚函数)。举个例子: class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) { } virtual ... -
C++多态性:虚函数的调用原理
2020-08-19 00:20:31多态性给我们带来了好处:多态使得我们可以通过基类的引用或指针来指明一个对象(包含其派生类的...一个函数说明为虚函数,表明在继承的类中重载这个函数时,当调用这个函数时应当查看以确定调用哪个对象的这个函数。 -
C++继承机制(三)——多继承、菱形继承、虚继承原理
2020-05-23 15:10:43虚继承 C++中的多继承: 1)基本语法:class SubClass : public SuperClass1, public SuperClass2 2)多继承可能出现的问题: 当父类与父类直接出现同名情况 此时访问这些父类间的同名成员需要加上父类作用域用于... -
【C++拾遗】 从内存布局看C++虚继承的实现原理
2015-08-27 16:49:10微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout。使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可。切换到cpp文件所在目录下输入... -
虚继承基本原理
2022-02-16 10:03:56二、从内存布局看虚继承原理 1、普通类的菱形继承:虚基类的成员会被拷贝两份,一模一样的,导致了空间的浪费; class A { public: int age=10; }; class B: public A { public: int b; }; class C : public A { ... -
c++之虚继承(多重继承的问题)
2020-06-13 13:56:11导引:多重继承和多继承 什么是多重继承:如图 什么是多继承:如图 继承中的特殊结构 菱形继承结构带来的问题,D会有两个A中的数据成员 class A { public: int a; }; class B:public A { public: int b; }; ...