C++对象模型:object
一、object
typedef struct{
float x;
float y;
float z;
}Point3d;
可以有以下方法打印上述类型字段:
- 定义函数
void print_point3d(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);
- 也可以由一个前置处理宏来处理
#define X(p,xval) (p.x)=(xval)
//...
Point3d pd;
X(pd, 0.0);
在C++中,Point3d
的实现方式如下:
- 可以采用独立的抽象数据类型ADT实现
class Point3d{
public:
Point3d(float x=0.0, float y=0.0, float z=0.0):x_(x),y_(),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();
}
- 或使用多层的class层次结构实现
class Point{
public:
Point(float x=0.0):x_(x){}
float x(){ return x_; }
void x(float xval){ x_=xval; }
private:
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; }
private:
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; }
private:
float z_;
};
- 更进一步,不管那种类型,都可以被参数化,坐标类型参数化
template<class type>
class Point3d{};
// 或如下,坐标类型和个数都参数化
/*
template<class type, int dim>
class Point{
public:
Point();
Point(type coords[dim]){
for(int index=0; index<dim; index++){
coords_[index]= coords[index]
}
}
type& operator[](int index){
assert(index<dim && index>=0);
return coords_[index];
}
type& operator[](int index)const{
}
private:
type coords_[dim];
};
inline template<class type, int dim>
ostream& operator<<(ostream& os, const Point<type,dim>& pt){
// print
}
*/
对于C++的封装,是否有布局上的成本
-
每个non-inline只会有一个函数实例,而每个拥有零个或一个定义的inline function则会在每个使用者中产生一个函数实例
-
C++布局和效率的负担主要在
virtual
关键字上
- 虚函数virtual function机制
用以支持执行期绑定runtime binding - 虚继承virtual base class机制
用以实现多次出现在继承体系中的基类,有单一且共享的实例
此外,还有一些多重继承下的额外负担,发生在“一个子类和其第二或后继的父类间的转换”之间
二、C++对象模型
class Point{
public:
Point(float xval);
virtual ~Point();
float x()const;
static int PointCount();
protected:
virtual std::ostream& print(std::ostream& os)const;
float x_;
static int point_count_;
};
简单对象模型
一个object对应一系列的slots,每个slot指向一个member
表格驱动对象模型
C++对象模型
-
非静态成员变量
-
静态成员变量
-
静态和非静态成员函数
-
虚函数
类生成一系列指向虚函数的指针,放在虚函数表vtbl
中
每个对象有一个指向vtbl
的指针vptr
,vptr
的设置和重置由每一个类的构造函数,析构函数和赋值运算符自动完成
每个类所关联的type_info object(用以支持RTTI),也由vtbl
指出,通常放在第一个slot
vptr
和vtbl
的使用沿用cfront编译器 -
加上继承
- 单一继承
class Library_materials{}; class Book:public Library_materials{}; class Rental_book:public Book{};
- 多重继承
class iostream: public istream, public ostream{};
继承关系可以指定为虚继承
class istream:virtual public ios{}; class ostream:virtual public ios{};
保证了在继承关系中只会存在一个基类的实例subobject
C++最初采用的继承模型并不运用任何间接性:基类的成员变量直接放在子类对象中
C++2.0引入的虚继承,为每个关联的虚基类加上指针
三、关键字差异
若没有C的8种整数需要支持,overloaded function的解决方案会更简单
若丢弃C的声明语法,就无需花时间判断下面到底是函数调用还是声明
void (*pf)(1024);
void (*pq)();
struct
关键字实现了C的数据抽象观念,class
关键字实现的是C++的ADT观念
将单一数组放在struct
的尾端,于是每个struct
对象将拥有可变大小的数组
struct M{
char pc[1];
};
int main(){
const char* str="";
struct M* m=(struct M*)malloc( sizeof(struct M)+strlen(str)+1 );
strcpy(m->pc, str);
return 0;
}
四、对象差异
C++程序设计范式programming paradigms
程序模型procedural model
抽象数据类型模型ADT model
面向对象模型object oriented model
完成多态polymorphic操作
class Library_materials{
public:
void check_in(){ std::cout<<"Library_materials"<<std::endl; }
};
class Book:public Library_materials{
public:
void check_in(){ std::cout<<"Book"<<std::endl; }
};
Library_materials thing1;
Book b;
thing1= b;
thing1.check_in(); // 实际调用 Library_materials::check_in()
通过pointer或reference的间接处理实现多态,前提:check_in()是虚函数
thing1
的定义反映的是ADT paradigm的行为
需要多少内存才能实现一个class object,一般而言包含如下:
- 非静态成员变量大小
- 由于alignment的需要而填补padding的空间,即对齐
- 为了支持
virtual
而产生的额外负担
class ZooAnimal{
public:
ZooAnimal(){}
virtual ~ZooAnimal(){}
virtual void rotate(){}
protected:
int loc;
char* name;
};
不同的指针类型,仅在于寻址大小不同,void*
的寻址大小是未知的,这也是为什么空指针不能访问对象
加上多态
class Bear:public ZooAnimal{
public:
Bear(){}
~Bear(){}
void rotate(){}
virtual void dance(){}
protected:
enum class Dances{};
Dances dances_known;
int cell_block;
};
定义变量
Bear b;
ZooAnimal* pz= &b;
Bear* pb= &b;
包含虚函数的类,在实例化前需要实现虚函数,否则提示undefined reference to vtable for Bear
指针pz
和pb
都指向对象b
的第一个字节,差别是pb
涵盖的地址包含整个Bear object,而pz
涵盖的地址只包含Bear object中的ZooAnimal subobject
若pz
想访问Bear
中的数据
经过一个显式的转换
(static_cast<Bear*>(pz))->dance();
或,更好的使用runtime operator,成本较高
if(Bear* pb2=dynamic_cast<Bear*>(pz)){
pb2->dance();
}
若调用
pz->rotate()
pz
的类型在编译期决定,其类型决定rotate()
所调用的实例
类型信息的封装并不是维护在pz
中,而是维护在link中,此link存在于object的vptr
和vtbl
之间
Bear b;
ZooAnimal za= b;
za.rotate();
为什么za.rotate()
调用的是ZooAnimal::rotate()
,而不是Bear::rotate()
za
只能是一个ZooAnimal
,多态的潜在力量不会出现在直接存取object这件事上
若初始化函数将一个object内容完整拷贝到另一个object中,那么为什么za
不指向Bear
的vtbl
因为编译器会确保,若object含有一个或多个vptr
时,则那些vptr的内容不会被base class object初始化或改变
一个pointer或reference之所以支持多态,是因为他们并不引发内存中的任何与类型有关的内存委托操作,改变的只是其所指向的内存的大小和内容的解释方式而已