深度探索 C++ 对象模型 03:数据成员

  • 即使一个类表面上是空的,它也内含一个隐式的 char,从而确保这个类的任意两个 object 不会在内存中占用相同的地址
  • 类的 nonstatic data member 放置其各个 object 感兴趣的数据,而类的 static data member 则放置类本身感兴趣的数据
    • nonstatic data member 直接存放在每一个 class object 中,对于继承而来的 nonstatic data member 也是如此
    • static data member 则存放在应用程序的一个 global data segment 中,不影响每个 class object 的大小
    • 在程序中,static data member 永远只有一份实体,即使类没有任何 object 实体,其 static data member 也已经存在

Data Member 的布局

  • 编译器会合成一些内部使用的 data members 以支持整个对象模型,比如 vptr
  • 编译器会将 vptr 安插在每一个内含 virtual function 的 class object 内
class A {};                           // sizeof(A) = 1,隐含一个 char
class B1 : virtual  public A {};      // sizeof(B1) = sizeof(B2) = 8,都隐含一个指向 virtual base class 
class B2 : virtual  public A {};      //                              subobject 的指针,但没有 char
class C  : public B1, public B2 {};   // sizeof(C) = 16,隐含 B1 内隐含的指针和 B2 内隐含的指针
class Point3d
{
private:
  double x;
  static array<Point3d*, 10>* p_arr;
  double y;
  static const int chunk_size = 250;
  double z;

public:
  Point3d(double, double, double);
  double get_x() const { return x; }
  void set_x(const double newx) { x = newx; }
};

nonstatic data member

  • nonstatic data member 直接存放在每一个 class object 中,且只能通过 (explicit or implicit) class object 访问,this 指针的使用便是 implicit 的访问
Point3d Point3d::translate(const Point3d& rhs)
{
  x += rhs.x;
  y += rhs.y;
  z += rhs.z;
}
// 转化为
Point3d Point3d::translate(Point3d *const this, const Point3d& rhs)
{
  this->x += rhs.x;
  this->y += rhs.y;
  this->z += rhs.z;
}
  • 编译器通过在 class object 的起始地址加上 nonstatic data member 的地址偏移量来访问 data member
  • 每个 nonstatic data member 的偏移量在编译时便可获知,因此存取 nonstatic data member 的效率与使用 C struct 一样
  • 虚拟继承会因为经由 base class subobject 存取数据成员而引入一层新的间接性

Data Member 与继承

没有多态的继承:不含 virtual function

  • 具体继承,不会增加空间或存取时间上的额外负担
class Point2d
{
public:
  Point2d(double x = 0.0, double y = 0.0)
    : _x(x), _y(y) { }
  
  double x() const { return _x; }
  double y() const { return _y; }
  void x(double xx) { _x = xx; }
  void y(double yy) { _y = yy; }

  Point2d& operator+=(const Point2d& rhs) {
    _x += rhs.x();
    _y += rhs.y();
    return *this;
  }
  // ...

protected:
  double _x, _y;
};

class Point3d : public Point2d
{
public:
  Point3d(double x = 0.0, double y = 0.0, double z = 0.0)
    : Point2d(x, y), _z(z) { }

  double z() const { return _z; }
  void z(double zz) { _z = zz; }

  Point3d& operator+=(const Point3d& rhs) {
    Point2d::operator+=(rhs);
    _z += rhs.z();
    return *this;
  }
  // ...

protected:
  double _z;
};

有多态的继承:含有 virtual function

  • 只有需要为继承链中的类提供一致的接口时才导入 virtual function
class Point2d
{
public:
  Point2d(double x = 0.0, double y = 0.0)
    : _x(x), _y(y) { }
  
  double x() const { return _x; }
  double y() const { return _y; }
  void x(double xx) { _x = xx; }
  void y(double yy) { _y = yy; }

  // virtual functions,既支持 Point2d 也支持 Point3d 
  virtual double z() const { return 0.0; }
  virtual void z(double zz) { }

  Point2d& operator+=(const Point2d& rhs) {
    _x += rhs.x();
    _y += rhs.y();
    return *this;
  }
  // ...

protected:
  double _x, _y;
};

class Point3d : public Point2d
{
public:
  Point3d(double x = 0.0, double y = 0.0, double z = 0.0)
    : Point2d(x, y), _z(z) { }

  double z() const { return _z; } // virtual
  void z(double zz) { _z = zz; }  // virtual

  Point3d& operator+=(const Point3d& rhs) {
    Point2d::operator+=(rhs);
    _z += rhs.z();
    return *this;
  }
  // ...

protected:
  double _z;
};
多态的代价
  • 导入 Point2d 的 vtbl,存放每一个 virtual function 的地址;vtbl 的元素数目比 virtual function 的数目多一两格,用来支持 runtime type identification
  • 在每个 class object 中导入一个 vptr 指针,指向该 class 的 vtbl,提供执行期决策,使每个 class object 能够找到相应的 vtbl
  • 扩展 constructor,使其能设置 vptr 的初值,让它指向正确的 vtbl
  • 扩展 destructor,使其能释放 vptr

多重继承 multiple inheritance

class Point2d
{
public:
  // virtual funtions and other interfaces
protected:
  double _x, _y;
};

class Point3d : public Point2d
{
public:
  // interfaces, no virtual funtions
protected:
  double _z;
};

class Vertex
{
public:
  // virtual funtions and other interfaces
protected:
  Vertex* next;
};

class Vertex3d : public Point3d, public Vertex
{
public:
  // interfaces, no virtual funtions
protected:
  double mumble;
};
// 由以上继承体系得到的 Vectex3d object 内先后包含一个 Point3d subobject 和
// 一个 Vertex subobject,其中 Point3d subobject 内又包含一个 Point2d subobject
  • derived class object 和 base class object 之间的转换
Vertex3d v3d;
Vertex*  pv;
Point2d* p2d;
Point3d* p3d;

// 程序转化
pv = &v3d;
// 转化为
pv = (Vertex*)((char*)&v3d + sizeof(Point3d));

// 不必转化
p2d = &v3d;
p3d = &v3d;

Vertex3d* pv3d;
Vertex*   pv;

// 程序转化,注意指针需要检查 0 值,但引用不需要
pv = pv3d;
// 转化为
pv = pv3d ? (Vertex*)((char*)pv3d) + sizeof(Point3d) : 0;

多重继承的对象布局

  • derived class object 中各 base class subobject 按声明次序排列
  • 所有 data member 的位置在编译时就已经确定,只需一个 offset 运算,因此不必付出额外的存取成本

虚拟继承 virtual inheritance

class ios {...};
class istream : public virtual ios {...};
class ostream : public virtual ios {...};
class iostream : public istream, public ostream {...};
  • class 如果内含一个或多个 virtual base class subobject,则将被分割成两个部分:不变局部和共享局部
    • 不变局部中的数据总是拥有固定的 offset(从 object 开头算起),因此可以直接存取
    • 共享局部表现的是 virtual base class subobject,这部分数据的位置在每次派生操作都会变化,因此只能被间接存取
class Point2d
{
public:
  // ...
protected:
  double _x, _y;
};

class Vertex : public virtual Point2d
{
public:
  // ...
protected:
  Vertex* next;
};

class Point3d : public virtual Point2d
{
public:
  // ...
protected:
  double _z;
};

class Vertex3d : public Vertex, public Point3d
{
public:
  // ...
protected:
  double mumble;
};
第一种 data member 布局方式:以指针指向 base class
  • 先安排好 derived class 的不变部分,再建立其共享部分
  • 对共享部分的存取:在每一个 derived class object 中安插一组指针,每个指针指向一个 virtual base class,然后通过这些指针存取继承而来的 virtual base class member
void Point3d::operator+=(const Point3d& rhs)
{
  _x += rhs._x;
  _y += rhs._y;
  _z += rhs._z;
}

// 程序转换
__vbcPoint2d->_x += rhs.__vbcPoint2d->_x;
__vbcPoint2d->_y += rhs.__vbcPoint2d->_y;
_z += rhs._z;
// 编译器自动为 Point3d object 生成一个指向 Point2d subobject 
// 的指针 __vbcPoint2d
Point2d* p2d = pv3d;
// 程序转换
Point2d* p2d = pv3d ? pv3d->__vbcPoint2d : 0;

第一种布局方式

第二种 data member 布局方式
  • 在 vtbl 中放置 virtual base class 的 offset,而非地址,也就是将 virtual function 和 virtual base class offset 混杂存放于 vtbl 中
  • vtbl 通过区分索引正负确定 virtual base class 和 virtual function,正索引指向 virtual function,负索引指向 virtual base class 的 offset

第二种布局方式

对于 virtual base class,最有效的运用方式是不带任何 data member

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值