C++ 对象模型 — 关于对象
关于对象
面向过程
在 C 语言中, 我们通常将 数据 和 处理数据的操作(函数) 分开声明, 也就是说语言本身没有支持 数据和函数 之间的关联性. 这种程序为典型的面向过程的程序.
// data declare
typedef struct point3d
{
float x;
float y;
float z;
} Point3d;
// print a pint, function declare
void Point3dPrint(const Point3d *pd)
{
printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
}
// or define a macro
#define Point3dPrint(pd) printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
抽象数据类型
在 C++ 语言中, 可以用独立的 抽象数据类型 实现.
class Point3d
{
public:
Point3d() = default;
Point3d(float x, float y, float z):_x(x), _y(y), _z(z) {}
Point3d(Point3d &&) = default;
Point3d(const Point3d &) = default;
Point3d &operator=(Point3d &&) = default;
Point3d &operator=(const Point3d &) = default;
~Point3d() = default;
float x() const {return _x;}
float y() const {return _y;}
float z() const {return _z;}
void Point3dPrint()const{
printf("(%g, %g, %g)", _x, _y, _z);
}
private:
float _x;
float _y;
float _z;
};
从上面的例子可以看出, 相对于面向过程而言, ADT 将数据和对数据的操作封装在一起了. 这样比面向过程的全局数据要好.
封装成本
直观上看,感觉 C++ 的会占用更多的内存成本. 但事实上并没有增加成本. 在上例中, Point3d 对象的大小只和数据成员大小相关, 成员函数虽然在类内声明,但是不出现在对象中, 每一个飞内联成员函数只会产生一个函数体.
当然如果说 C++ 的类对象和 C 中的结构体大小总是相同, 也是不对的. 这里主要是用于实现运行时多态的 virtual 技术带来的 额外开销:
- 虚函数机制: 用来支持有效的执行期绑定
- 虚基类: 菱形继承体系中被多次继承的基类只保存一个共享实体
C++ 对象模型
在 C++ 中, 有两种数据成员: 静态和非静态, 以及三种成员函数: 静态, 非静态和虚函数. 如下例子:
class Point{
public:
Point(float x);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream&
print(ostream &os)const;
protected:
float _x;
static int _point_count
};
```
简单对象模型
对象由一系列 slots 组成, 每一个 slot 指向一个成员, 成员按其声明次序被制定一个 slot.
成员本身并不防止对象中, 而是将指向成员的指针防止 slots 中, 这样可以避免成员有不同的类型需要不同 的内存空间带来的问题.
### 表格驱动模型
为了对所有的对象都有一致的表达方式,将成员相关的信息抽取出来, 放在数据成员表和成员方法表中. 类对象 本身则汗指向这两个表格的指针.
成员函数表的观念为 C++ 对象模型支持虚函数提供有效的方案
C++对象模型
- 每个 class 产生出指向虚函数的指针, 放在表格之中. 称为虚函数表 vbtl
- 每个类对象添加一个指针指向相关的虚函数表, 这个指针称为 vptr. vptr 的设定和重置由构造函数,析构函数和赋值函数自动完成; 每个 class 所关联的 type_info 也由虚函数表指出, 通常为第一个 slot
- 优点: 空间和时间存取的效率
- 缺点: 非静态数据成员的修改需重新编译
继承
单一继承
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 {};
在虚继承中,不管基类在继承链中被派生多少次,都只会保存一个实体.
对象模型对程序的影响
- 现有程序代码必须修改
- 必须加入新的程序代码
关键字带来的差异
主要原因还是为了维护与 C 之间的兼容性。如 c++ 中本可以不用 struct, 但为了兼容 C 还是保留了 struct 关键字。
对象的差异
C++ 支持三种编程范式。
程序模型
主要是为了兼容 C.
抽象数据类型模型
抽象是和一组比表达一起提供,而其运算定义仍然不知道。
面向对象模型
一些彼此相关的类型通过一个抽象的基类被封装起来。
多态方法:
- 隐式转换
- 虚函数机制
- dynamic_cast 和 typeid
- 模板