为什么C++可以“自动完成”这么多事情,多态的底层实现是什么样子的,理解了C++的对象模型后,可以写出比较没有错误且比较有效的代码。
目录
1、C与C++代码差异
在C语言中,“数据”和“处理数据的函数”是分开声明的,像这样子:
typedef struct point3d
{
float x;
float y;
float z;
}Point3d;
想要打印一个Point3d,可以直接定义一个这样的函数
void Point3d_print(const Point3d *pd)
{
printf("(%f, %f, %f)",pd->x, pd->y, pd->z);
}
对特定点的坐标值,可以直接进行存取
Point3d pt;
pt.x = 0.0;
在C++语言中,“数据”和“处理数据的函数”是合并在一起的,像这样子。
class Point3d
{
public:
Point3d(float x = 0.0,float y = 0.0,float z = 0.0)
:_x(x),_y(y),_z(z){}
float x() {return _x;}
float y() {return _y;}
float z() {return _z;}
void x(float xval){_x=xval;}
private:
float _x;
float _y;
float _z;
};
inline ostream &
operator <<(ostream &os,const Point3d &pt)
{
os << "(" <<pt.x()<<","
<<pt.y() <<","<<pt.z()<<")";
}
利用C++的继承特性,或是这样的代码
class Point
{
public:
Point(float x = 0.0):_x(x){}
float x(){return _x;}
void x(float xval){_x=xval;}
protected:
float _x;
};
class Point2d : public Point
{
public:
Point2d(float x=0.0,float y=0.0):Point(x),_y(y){}
float y(){return _y;}
void y(float yval){_y=yval;}
//...
protected:
float _y;
};
class Point3d : public Point2d
{
public:
Point3d(float x=0.0,float y=0.0,float z=0.0)
:Point2d(x,y),_z(z){}
float z(){return _z;}
void z(float zval){_z=zval;}
//...
protected:
float _z;
};
利用C++的模板特性,或是这样的代码
template <class type>
class Point3d
{
public:
Point3d(type x=0.0, type y=0.0, type z=0.0)
: _x(x),_y(y),_z(z){}
type x(){return _x;}
void x(type xval){_x = xval;}
//...
private:
type _x;
type _y;
type _z;
};
很显然,C和C++在程序风格上的差异巨大,那么从软件工程的角度看,C++有什么好处和坏处呢?C++的好处是:
1)封装,外部无法直接访问内部数据,有利于软件工程快速集成。
2)代码复用,继承和类提高代码开发效率,降低成本。C++的坏处在于并不精瘦和简洁,难理解(相对C而言)。
2、C++封装布局成本
C++使用了class关键字对数据和函数进行了封装,那么封装后的成本是多少?有没有额外的内存增加,有没有额外的访存时间开销?答案是class Point3d并没有增加以上的成本,三个data member 直接内含在一个class object中,就像C 的struct一样。而member function 虽然在class中声明,但不会出现在object中。每一个non-inline member function 只会诞生一个函数实例。
你会看到C++的额外开销是由virtual 引起的,包括:
- virtual function 机制,用以支持一个有效率的“执行期绑定”。
- virtual base class 机制,用以支持“多次出现在继承体系中的base class,有一个单一而被共享的实例”。
3、C++对象模型
在C++中,有两种class data members: static 和nonstatic,以及三种class member functions: static、nonstatic和virtual。已知下面的class Point声明:
class Point
{
public:
Point(float xval);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream&
print(ostream &os)const;
float _x;
static int _point_count;
};
那么它的模型是什么样子呢?Nonstatic data members被配置于每一个class object之内,static data members则被存放在个别的class object之外。virtual function 则以两个步骤支持:
1)每个class 产生出一堆指向virtual functions的指针,放在表格之中。这个表格称为virtual table(vtabl)。
2)每个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的setting和reset ting,都是由每一个class的constructor、destructor和copy assignment运算符完成。每一个class关联的type_info object(用来支持runtime type identification,RTTI)也由virtual table被指出来,通常放在表格的第一个slot中。
图1-1 C++对象模型
4、对象模型内部转换简介
对于以上C++对象模型,对于函数实现有什么影响呢?举个例子,例如class X的定义如下:
class X
{
public:
X(const X & other){}
virtual ~X(){}
virtual void foo(){}
};
那么对于一下的函数,内部可能转换成什么样子呢?
X foobar()
{
X xx;
X *px = new X;
//foo()是一个virtual function
xx.foo();
px->foo();
delete px;
return xx;
};
可能转换成如下代码
//可能内部转换结果
//虚拟C++代码
void foobar(X &_result)
{
//构造_result
//_result 用来取代local xx
_result.X::X();
//扩展 X *px = new X;
px = _new(sizeof(X));
if( px != 0)
px->X:X();
//扩展xx.foo()但不能使用virtual 机制
//以 _restult取代xx
foo(&_result);
//使用virtual 机制扩展px->foo()
(*px->vtbl[2])(px);
//扩展delete px;
if(px != 0)
{
(*px->vtbl[1])(px);//destructor
_delete(px);
}
//无须使用named return statement;
//无须摧毁local object xx
return;
};