虚函数和虚拟继承的内存分布

一.虚函数

(1)C++中的虚函数的主要作用:实现了多态的机制。

(2)多态:用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

(3)多态要基于函数重载,所以如果子类没有重载父类的虚函数那是一件毫无意义的事情。

二.虚函数表

1.虚函数表:虚函数表C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

如图:

虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

2.虚函数在虚函数表中的存放原则

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面

3)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

4)没有被覆盖的函数依旧。

3.多重继承

1) 每个父类都有自己的虚表。

2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)     这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

三.进阶

(1)单一的一般继承

1)虚函数表在最前面的位置。

2)成员变量根据其继承和声明顺序依次放在后面。

3)在单一的继承中,被重写的虚函数在虚函数表中得到了更新。

x

(2)多重继承(只是比上面的多重继承多了个成员变量)

1)每个父类都有自己的虚表。

2)子类的成员函数被放到了第一个父类的表中。

3)内存布局中,其父类布局依次按声明顺序排列。

4)每个父类的虚表中的f()函数都被重写成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

(3)重复继承

最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

D d;
d.ib = 0; //二义性错误
d.B1::ib = 1; //正确
d.B2::ib = 2; //正确

注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

(4)重复虚拟继承-把上述的“重复继承”的B1和B2继承B的语法中加上virtual 关键,就成了虚拟继承

四.利用虚函数表做的坏事儿

(1)任何试图使用父类指针调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,但我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。

(2)如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数。

<有个疑惑:为什么最后一个多重虚继承B的地址在最后?请大家指教一下,我也下去再好好研究一下>

时间: 2024-10-14 18:49:47

虚函数和虚拟继承的内存分布的相关文章

深入探索C++对象象模型--拷贝构造函数 &amp;&amp;多重继承 虚拟继承 内存分布

拷贝构造函数 如果没有定义拷贝构造函数,那么编译器会自动生成一个拷贝构造函数,但是这个拷贝构造函数是有一定限度的. 一般来说这个拷贝构造函数是按照位直接拷贝的,但是在有些情况下这种初始化是有问题的,在特殊的四种情况下是有问题的,在有问题的情况下,可以举例说明. 如果一个有多态性质的对象,子类赋值给父类,调用了拷贝构造函数,这个时候就需要给父类的虚拟函数表重新分配,使得虚拟函数表和子类不是同一个,这样bitwist就不能有效 对于在函数参数中调用拷贝构造函数,参数是实参的一根拷贝,对于函数的返回值

C++ 继承之虚继承与普通继承的内存分布

仅供互相学习,请勿喷,有观点欢迎指出~ class A { virtual void aa(){}; }; class B : public virtual A { char j[3]; //加入一个变量是为了看清楚class中的vfptr放在什么位置 public: virtual void bb(){}; }; class C : public virtual A { char i[3]; public: virtual void cc(){}; }; class C1 : public A

C++ 无虚函数的单继承内存模型

C++类因为有继承的存在要比C时代的struct复杂得一些,特别是加上有虚函数的时候,以及多继承等这些特性更是令其内存布局变得面目全非.说实在的我也把握不了,我只是在一个实际的平台上进行了一些探索而已,并用此篇笔记将我的探索成果记录下来. 虽然说有些东西在C++标准里面没有规定如何做,不同的实现可能会有不同的作法,但是了解一个实际的系统是如何做的也会有益于我们更加深入的了解C++或者举一反三地理解其他的实现,而且如果我们了解了自己所用的系统上的具体实现的话,就可以对其为所欲为. 没有虚函数单继承

普通继承和虚拟继承的内存布局原则

环境: windows xp 3     VC2008 如果类A拥有虚函数,而类B普通继承自类A,那么一个类B的对象在内存布局里:类B的虚表会将类A虚表合并覆盖,然后先排列类A的数据,再排列类B的数据 如果类B虚拟继承自类A,那么一个类B的对象在内存布局里:类B的虚表和内容都不会将类A的合并,在类B的虚表之后插入一个虚基表,通过这个表来访问类A.

C++ 深入了解 函数, 虚函数, 单继承,多继承,指针,引用。

最近又开始写项目服务器部分了, 再次接触了C++ 有了一些更深入的体会.记录一下,以免忘记~  之前学习C++ 差不错都是靠死记, 记住C++的用法,C++的特性,然后去使用.没有从根本上理解,导致 几年不用C++,就已经完全忘记,然后又要花好长时间去记忆,使用.所以要真正做到学会C++,必须要从根本上了解,这样才不至于有会忘记, 而且使用起来会更的心应手. 1.明确了一些定义 关于指针, 刚开始学习指针的时候,总是一些模糊的印象,想不清楚具体是什么, 就知道死记用法.仅仅知道指针能指向一个对象

More Effective C++----(24)理解虚拟函数、多继承、虚继承和RTTI所需的代价

Item M24:理解虚拟函数.多继承.虚继承和RTTI所需的代价 C++编译器们必须实现语言的每一个特性.这些实现的细节当然是由编译器来决定的,并且不同的编译器有不同的方法实现语言的特性.在多数情况下,你不用关心这些事情.然而有些特性的实现对对象大小和其成员函数执行速度有很大的影响,所以对于这些特性有一个基本的了解,知道编译器可能在背后做了些什么,就显得很重要.这种特性中最重要的例子是虚拟函数. 当调用一个虚拟函数时,被执行的代码必须与调用函数的对象的动态类型相一致:指向对象的指针或引用的类型

虚函数 继承 多态

单继承与Data Members 在C++的继承模型中,base class members和derived class members的排列顺序并为强制规定.不同的编译器可能有不同的布局安排.大部分情况下,base class members会安排在derived class members的前面,但base class是virtual base class(base class存在virtual function)除外. 只有继承没有多态 考虑如下程序: class Point2d { pu

C++ 虚函数的内存分配

1.无继承的普通类: 在有虚函数的情况下类会为其增加一个隐藏的成员,虚函数表指针,指向一个虚函数表,虚函数表里面就是类的各个虚函数的地址了.那么,虚函数表指针是以什么模型加入到类里面的,虚函数表里面又是怎么安排的呢.简单来看下就可以知道了. #include"stdafx.h" #pragma pack(8) class A{ public: int a; double a2; A() :a(0xaaaaaaaa), a2(0){} virtual void funA2(){} vir

C++对象内存模型2 (虚函数,虚指针,虚函数表)

从例子入手,考察如下带有虚函数的类的对象内存模型: 1 class A { 2 public: 3 virtual void vfunc1(); 4 virtual void vfunc2(); 5 void func1(); 6 void func2(); 7 virtual ~A(); 8 private: 9 int m_data1, m_data2; 10 }; 11 12 class B : A { 13 public: 14 virtual void vfunc1();; 15 vo