一个人不应该用猜的方式,或是等待某大师的宣判,才确定『何时提供一个复制构造函数而何时不需要』。
C 语言是一种面向过程语言,我们写一个三维坐标结构体,并打印:
typedef struct point3d {
float x;
float y;
float z;
} Point3D;
// 函数操作
void Point3d_print( const Point3D *pd) {
printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
}
// 宏定义
#define Point3D_print(pd) printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
C++ 是一种联邦制语言,我们使用其面向对象的部分重写上面的代码:
class Pont3D {
public:
Point3D( float x = 0.0, float y = 0.0, float x = 0.0)
: _x(x), _y(y), _z(z) {}
float x() { return _x; }
float y() { return _y; }
float z() { return _z; }
// ... etc ...
private:
float _x;
float _y;
float _z;
};
inline ostram& operator<<( ostream &os, const Point3D &pt ) {
os << pt.x() << ", " << pt.y() << ", " << pt.z();
};
或者使用继承的方式,从一维坐标的类派生出二维,进而派生出三维坐标;或者使用模板的方式做进一步的抽象。但是无论哪种方式,都会发现用 C++ 写起来要比 C 复杂得多。有很多人可能会认为,既然写法上复杂了很多,那么程序的执行效率必然会有所下降。但是实际情况是,C++ 的写法并没有增加成本,这和 C++ 的内存布局有关。C++ 在布局和存取时间上的额外负担主要是由 virtual 产生的,主要包括虚继承与虚函数。
C++ 对象模型
假设有一简单的类 Point
class Point {
public:
Point( float x );
virtual ~Point();
float x() const;
static int PointCount();
private:
virtual ostream& print( ostream &os ) const;
float _x;
static int _point_count;
}
如果要设计一套模型来存储这个类,我们会怎么设计呢?
因为类中每一个成员的类型不同,需要的存储空间也就不同,要解决这个问题,我们可以为每一个数据成员以及函数成员创建一个 slot,这个 slot 中存储的都是成员的指针(图片摘自《深度探索 C++ 对象模型》):
这就避免了存储的问题,但是所有成员都多了一次查找过程。
第二种方式是将数据成员及函数成员分别存在两个表中,数据成员表存放具体的数据,函数成员表中存放各个成员函数的 slot,slot 中存储函数地址,这样虽然很条理,但是效率更低了。
C++ 使用的对象模型从简单对象模型派生而来,并对内存空间和存取时间做了优化。在这个模型中,非静态数据存放在每个类对象中,静态数据成员、静态及非静态函数成员存放在类对象之外,虚函数采用了如下的方式进行支持:
- 每个类创建一张虚表 (vtbl),虚表中存放指向虚函数的指针
- 类对象存储指向虚表的虚指针 (vptr)
虚指针的设定和重置由每一个类对象的构造函数、析构函数和赋值运算符自动完成,此外,虚表的第一个 slot 存放 type_info,用于支持 RTTI 等特性(图片摘自《深度探索 C++ 对象模型》):
这个模型的主要优点在于空间和存取时间的效率,但由于非静态数据成员存储在类对象内部,因此一旦涉及到这部分数据的变化,都需要重新编译。
在了解了 C++ 的内存布局后,我们可以通过指针来获取虚表中的虚函数成员,并通过函数指针调用虚函数:
typedef void(*Fun)(void);
Fun pFun = nullptr;
Base b;
int **vptr = (int**)&b;
for (int i = 0; (Fun)vptr[0][i] != nullptr; ++i ) {
pFun = (Fun)vptr[0][i];
pFun();
}
可见,C++ 除了使用虚函数需要通过指针多查找一次,使用类的数据成员时,相对于 C 语言来说并不会增加开销。
有了虚表,就很容易实现多态,只要在构建派生类对象模型时,在虚表中覆盖掉重写的虚函数即可。
参考资料:
- C++ 对象的内存布局 —— 陈皓
- 深度探索 C++ 对象模型 —— Stanley B.Lippman