C++对象的内存模型

来源:互联网 发布:博迅软件 编辑:程序博客网 时间:2024/06/05 23:52

看了C++ under the hood之后,C++对象的内存模型是这样的:涉及到虚的,就需要额外的存储空间。虚这里指的是虚函数和虚继承,额外的存储是对象内部需要存储虚函数表指针和虚基类表指针。

一个类的内存的分布在VC下是这样的:先是非虚基类子对象(包括非虚基类的虚函数表指针和成员),接着是虚基类表指针,接着才是该类本身扩展的成员,然后是虚基类子对象。该类的虚函数如果在基类中出现,会在对应的虚函数表中的位置对基类的进行替代。而不曾在基类中出现的虚函数,会加到最前边的非虚基类子对象的虚函数表的尾部,所以如果没有非虚基类,则在起始位置新建一张虚函数表,用来保存这些多余的虚函数的位置。


一般的,初始化的顺序是按继承的顺序,从上到下,从左到右调用基类的构造函数(虚继承子对象会破坏这个规则,最先调用),然后是成员对象的构造函数,然后是虚函数表和虚基类表指针的初始化,最后是其他成员的初始化。另外,对于虚继承,虚继承子对象如果没有默认构造函数,在每一层的每一个派生类中都要显示的调用其构造函数,此时如果其基类已经调用了虚继承子对象的构造函数,在派生类的构造函数的初始化列表中,仍然要显示的调用,其基类的构造函数中对虚继承子对象的构造函数的调用将忽略。


class A;    //A假如没有默认的构造函数class B:virtual public Aclass C:virtual public Aclass D:public B, virtual public C    //在D的初始化列表中仍然要显示的调用A的构造函数(B、C构造函数的对A构造函数的调用将忽略)


也就是说,须基类的构造函数,在每一层的派生类的构造函数中都会被调用(A没有默认的构造函数,需要手写调用;有默认的,编译器安插A的构造函数在D的构造中);也就是说,上边D的构造函数中,仍然会调用A的构造函数,而不是将A的非虚继承中,包裹在B中或者C中,那样D只需要调用B的和C的就行;但是虚继承,D的还是会调用A的,而忽略在B和C中对A的调用。

利用这个原理,如果A的构造函数为private,假设B是A的友元,同时,B虚继承于A;那么,B将是一个无法被继承的类;因为如果C继承于B,那么C的构造函数中会去调用A的构造函数,这是不允许的。而且这里B可以正常在栈上构造,而普通的防治被继承的方法实际上使用的是单例的模式。

这就是C++中的黑科技。


这里也回顾一下C++的访问控制:public 是面向公众的,protected 是留给孩子的,private 是对朋友开放的(当然从内存上来看)。需要说明的是,孩子又有三种,当然从内存上来看,无论哪种孩子,派生类对象的内存里都有一个完成的基类子对象。默认情况下,三种孩子对于基类中的public 和 protected,继承过来知道,public 孩子变成自己的public 和 protected, protected 孩子都变成自己的protected,private 孩子都变成自己的private ;当然可以用using 关键字改变这种默认行为。

另外,如果不是为了多态,在派生类和基类中使用相同的函数名是个糟糕的设计;是虚函数而且必须函数签名相同才能是多态。其他情况下,基类的指针(或者引用)指向派生类,永远只能调用基类中定义过的函数,当然是函数名相同,签名不同,仍然是不同的函数。所谁的指针调用谁的函数。派生类的指针将无法调用与派生类中定义的同名的基类中的函数--> 这是个糟糕的设计,当然任何在访问权限内,总是可以用域运算符来访问基类的(所有的访问前都可以加域运算符,只是不简洁)。

为了无心造成的这个设计,C++11推出两个新的关键字:final 和 override;final修饰类是,表示这个类不能被继承。final 在基类中修饰虚函数,表示派生类中不能再重写这个虚函数,否则会出错;override 在派生类中修饰虚函数,表示这个虚函数是对基类的重写,否则会出错。



0 0
原创粉丝点击