C++对象模型:object

一、object

typedef struct{
  float x;
  float y;
  float z;
}Point3d;

可以有以下方法打印上述类型字段:

  1. 定义函数
void print_point3d(const Point3d* pd){
  printf("(%g,%g,%g)", pd->x, pd->y, pd->z);
}
  1. 若要更有效率,可以定义一个宏函数
#define Point3d_print(pd) \
  printf("(%g,%g,%g)", pd->x, pd->y, pd->z);
  1. 也可以由一个前置处理宏来处理
#define X(p,xval) (p.x)=(xval)
//...
Point3d pd;
X(pd, 0.0);

在C++中,Point3d的实现方式如下:

  1. 可以采用独立的抽象数据类型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();
}
  1. 或使用多层的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_;
};
  1. 更进一步,不管那种类型,都可以被参数化,坐标类型参数化
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++的封装,是否有布局上的成本

  1. 每个non-inline只会有一个函数实例,而每个拥有零个或一个定义的inline function则会在每个使用者中产生一个函数实例

  2. 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的指针vptrvptr的设置和重置由每一个类的构造函数,析构函数和赋值运算符自动完成
    每个类所关联的type_info object(用以支持RTTI),也由vtbl指出,通常放在第一个slot
    vptrvtbl的使用沿用cfront编译器

  • 加上继承

    1. 单一继承
    class Library_materials{};
    class Book:public Library_materials{};
    class Rental_book:public Book{};
    
    1. 多重继承
    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
指针pzpb都指向对象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的vptrvtbl之间

Bear b;
ZooAnimal za= b;
za.rotate();

为什么za.rotate()调用的是ZooAnimal::rotate(),而不是Bear::rotate()
za只能是一个ZooAnimal,多态的潜在力量不会出现在直接存取object这件事上

若初始化函数将一个object内容完整拷贝到另一个object中,那么为什么za不指向Bearvtbl
因为编译器会确保,若object含有一个或多个vptr时,则那些vptr的内容不会被base class object初始化或改变

一个pointer或reference之所以支持多态,是因为他们并不引发内存中的任何与类型有关的内存委托操作,改变的只是其所指向的内存的大小和内容的解释方式而已

posted @ 2024-11-01 20:11  sgqmax  阅读(1)  评论(0编辑  收藏  举报