“继承”与Data Member——《Inside The C++ Object Mod》学习笔记

来源:互联网 发布:mac最新系统怎么升级 编辑:程序博客网 时间:2024/05/01 20:15
Data语意学 ---------“继承”与Data Member

class Point2D {
public:
     float _x, _y;
     char _2d;
};

class Point3D : public Point2D{
public:
     float _z;
};

上面这两个对象的内存布局如下:

如果是32位的操作系统,默认是按照4字节对齐。所以Point2D会有3个字节的填充。Point3D继承自Point2D就会把Point2D的
成员对象都继承下来,包括其填充的3个字节。在非虚继承的情况下,派生类对象的大小就是等于基类对象的大小加上派生类本身成员对象的大小(如果派生类有虚函数,则还包括了vptr)。至于为什么Point3D需要Point2D那填充的3个字节空间。因为如果把Point2D的对象复制到Point3D对象上会导致不是我们所期望的结果。如下:

这里的_z的内存区域被覆盖了。在没有虚函数和多重继承,虚继承的情况下,对象成员的在内存空间的表示较简单。
Point3D* p3 = new Point3D;
Point2D* p2 = p3; //直接把p3的值赋给p2就可以了。 
而且也与C语言的struct是兼容的。在C++标准中规定,同一个访问段内成员在内存空间的摆放顺序是按照其声明顺序的。但是不同访问段内顺序则没有规定。即private 和public段内的成员在内存中顺序标准没有明确规定,由编译器自己决定。
如果派生类里有虚函数,则情况会大有不同。
class Point3D : public Point2D{
public:
     float _z;

     virtual void f() {}
};
此时会在Point3D中安插一个指向虚函数表virtual table的指针vptr,至于vptr的位置,c++标准也没有明确固定,可以放在开头,或者末尾,甚至是成员之间。不过现在的编译器不是放在开头,就是放在末尾处。
以VS编译器为例,Point3D的内存布局图

 
Point3D* p3 = new Point3D;
Point2D* p2 = p3; //在这里指针需要通过转换了。 需要一定的开销。
伪代码:
p2 = (Point2D*)((char*)p3 + sizeof(_vptr));
所以如果C++的对象要去兼容C的struct结构体,最好的方式是通过组合的方式去实现,并通过conversion 操作去转换。例子如下:
struct Point2D {
public:
     float _x, _y;
     char _2d;
};

class Point3D  {
public:
    //使用_p来完成一些事
     int X() { return _p._x; }
     ......
    operator Point2D() { return _p; }  //转换
private:
     Point2D _p;
};

原创粉丝点击