c++标准中有这样一个规定:
"no object shall have thesame address in memory as any other variable".
由此规定,导致几乎所有的编译器对一个空类都会添加一个char。
那什么是一个空类呢:
·Has static members /member function
·Has no virtual function
这样的类就是一个空类。
由于编译器对每一个空类都会添加一个char,来保证每一个class对象都具有不同的地址 ,那么在不同的继承环境下上就会导致一些问题。
我们主要讲两个一个是空类的单一继承,另外一个是空类的虚拟继承:
对于第一种情况,我们用一个空类去继承另外一个空类。
我们已知Empty_A大小为一个字节,那么根据vs的编译结果来看Empty_B大小为四个字节。
对于第二种情况来说如下图
此时A,B,C,D的所占内存大小为1,8,8,12
有的编译器会显示1,8,8,16.
出现这两种情况原因是关于Allgnment的限制,和不同编译器对Allgnment的解决方式不同所导致不同的优化结果。
1,8,8,12.这种情况是现在主流编辑器的情况,为什么这么说呢?
因为对于C++OO来说:
“一个empty virtualbase class,被视为derived interface class object的一部分。”,也就是说我们并不会用empty virtual base class定义任何对象。所以就可以无视c++标准的规定。所以在derived 的情况下,并不会占用空间。
接下来说说关于数据成员的绑定(thebinding of data member)
关于数据成员绑定有些东西没必要谈,因为有些成员绑定的演进是由于程序员不同的写法而造成了编译器的对这个行为的“妥协”。只要说一句“对于一个类成员函数的分析会被延迟到"}"的出现才开始分析的”。
关于其他说一下typedef
typedef int length;
class a {
public:
length member;
private:
typedef long length;
};
在这个class 中,我们可能会有一些因直觉而失去正确的判断,认为class member length的类型为 long,其实是global typedef。
因此在typedef的定义中应该避免nested(嵌套) typedef。
:using的情况也是和typedef一样。
我们再来看看data member的布局(datamember layout)
//c++class类对象单独享有类成员(no-static),而共享类成员函数。
对于data member的布局我们看看c++11怎么说:
摘自c++11 standrad
No-staticdata members of class with the same access control are allocated so that latermembers has highter addresses with a class object
access(指的是private,public,protected等区段)
而对于相同区段的重复定义则,编译器可以按照自己的方式去定义不同区段的排列顺序,实际上大家没有一个反人类的心去改变顺序,而是选择直接按照区段顺序定义data member。
这里我们不说多态,与多继承,虚拟继承的情况。
谈一下类成员的存取,对于no-static members的存取就一定要谈到面向对象的精髓-多态,说到多态貌似不谈设计模式就感觉少了点什么,好吧,接着类成员的存取接着说,指针调用类成员函数与类成员和直接调用成员
函数与类成员产生的差异与否都是和多态与virtual derive 有关。
关于c++ virtual我可能会花时间,写一篇博文来巩固自己的认知,这里就不再赘述。
对于static members来说,取staticmembers 的地址就是取得它位于内存中的地址并不是偏移量,它可以看做是global的。但是被class限制,此时的类具有等效于一个namespace。
关于单一继承与多继承:
对于与单一继承,c++保证在derived class中保证 base class object 对象的完整性,这就是说,因为Allgnment而导致的字节扩充也会被继承下来。(空类是其中特殊的一环)
而单一继承的问题是对于派生类的主要问题发生在derived class object向下转换的时候可能需要编译器的介入。
一个多重类的派生对象在将地址指向最左端的base class 时候,情况与单一继承相同(因为保证了对象的完整性),而赋予第二个对象的时候就需要调整指针位置。
看个例子:
class A {....};
class B :A {...};
class C {...};
class D :B, C {...};
对于
D想B的转化我们直接将D的地址交给B即可。
而对于向C的转化我们需要做如下操作。
D* d=new …;
C* c;
c =d;->-> c=(C)((char*)d + sizeof(b));
//(这个转化对于现在来说并不是很标准,c++11提供的转化更加专业)
当d为null pointer的时候,就会遇到麻烦,我们稍稍改进一下:
c=d
?(C)((char*)d + sizeof(b))
: 0;
关于虚拟继承,将会在下一篇文章和data members pointer中一并写出。
参考资料:深度探索c++对象模型(侯捷译著),BOOST程序库探秘。 c++11 standrad
与之相关的资料:Bjarne Stroustrup's homepage:http://www.stroustrup.com/index.html