- 即使一个类表面上是空的,它也内含一个隐式的
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