C++对象模型——关于对象


前言

最近在学习《深度探索C++对象模型》,真心觉得这是一本很不错的书籍,看了之后觉得自己对对象的内存布局、构造、析构操作以及虚函数的调用等有了进一步的了解,对于一些C++规则存在的内在原因也算是知其所以然了。比如说C++2.0之前要求inline函数“类内声明,类外定义”,原因是在C++早期的编译器上,如果inline函数中存取的data member在该函数之后声明,则data member的绑定会出错。因此,C++2.0规定一个内联函数实体,在整个类声明未被完全看见之前,是不会被评估求值的,也就是说inline函数体中的数据绑定放到类声明之后执行。
此篇记录原书第一章的学习笔记。第一章主要介绍了C++与C之间因不同的设计理念而带来的封装上的差异以及C++支持的三种不同的程序设计范式,并简要介绍了C++的对象模型。


C这种程序性的语言将“数据”和“处理数据的操作(函数)”分开来声明,而C++将数据和函数封装为抽象数据类型(ADT)。

一、加上封装后,布局成本增加了多少?

封装本身并不会增加任何成本。data members直接内含于一个class object中,就像C中的struct一样。而member functions虽然含在class的声明中,却不出现在object中(更像是类命名空间中的普通函数),每个non-inline member function只会诞生一个函数实例。

C++在布局以及存取时间上主要的额外负担是由virtual引起的,包括:

  • virtual function机制:需要保存vtbl和透过vtbl找到函数地址;
  • virtual base class机制:需要透过指针(vbptr)指向的vbtbl来找到基类的成员,vbtbl中存放本类与虚基类之间的offset。

另外还有一些多重继承下的额外负担,发生在“一个derived class和其第二或后继之base class的转换”,这个过程中需要指针的偏移。

二、C++的对象模型

在这里插入图片描述
(1)首先,

  • Nonstatic data members被配置于每一个class object之内;
  • Static data members则被存放在所有的class object之外;
  • Static和Nonstatic function members 也被放在所有的class object之外。

(2)其次,Virtual function机制则由以下的2个的两个步骤来支持:

  • 每一个class产生出一系列指向Virtual functions的指针,放在一个被称为virtual table(vtbl, vtable)的表格中;
  • 每一个class object被添加了一个指针vptr,指向相对应的vtable。vptr的设置由编译器全权负责(编译器通过向class的constructor、destructor和copy assignment运算符中添加代码实现vptr的设定和重置),程序员无需关心。

(3)虚机制的实行仰仗于C++的RTTI(运行时类别识别),每个class相关联的type_info object的指针也保存在vtbl的第一个slot中。type_info是C++ Standard所定义的类型描述器的class名称,它重载了operator=()、operator!=()、name()等成员函数。运行时,两个类型描述器被交给一个runtime library函数,若两个类型之间是相等或is-a关系,则返回真正的地址,否则返回0。

(4)需要清楚的明白一点是:
一个 vtable 对应 一个 class , 一个 vptr 才对应 一个 class object,必须区分开这2个概念。

(5)引入继承后的对象模型成本:

  • 如果是普通的继承,父对象被直接包含在子对象里面,这样对父对象的存取也是直接进行的,没有额外的成本;
  • 如果是虚拟继承,则父对象会由一个指针被指出来,这样的话对父对象的存取就添加了一层间接性,必须经由一个指针(vbptr)来访问,添加了一次间接的额外成本。

三、struct关键字

(1)C++优先判断一个语句为声明:当语言无法区分一个语句是声明还是表达式时,就需要用一个超越语言范围的规则 —— C++优先判断为声明。

(2)struct和class关键字的意义:

  • 它们之间在语言层面并无本质的区别,更多的是概念和编程思想上的区别。
  • struct用来表现那些只有数据的集合体POD(Plain OI’ Data)、而class则希望表达的是ADT(abstract data type)的思想;
  • 由于这2个关键字在本质是无区别,所以class并没有必须要引入,但是引入它的确非常令人满意,因为这个语言所引入的不止是这个关键字,还有它所支持的封装和继承的哲学;
  • 可以这样想象:struct只剩下方便C程序员迁徙到C++的用途了。

(3)因为C++只保证处于同一个access section的数据,必定以其声明顺序出现在内存布局当中。所以C的一些伎俩在C++中不一定有效,比如把单一元素的数组放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组。

(4)与C兼容的内存布局: 组合,而非继承,才是把 C 和 C++ 结合在一起的唯一可行的方法。组合就是在一个类中以另一个类的对象作为数据成员。C struct在C++中的一个合理用途就是,当你要传递”一个很复杂的C++ class object的全部或部分“到某个C函数去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。

四、对象的差异

(1)C++程序设计模型直接支持三种程序设计范式:

程序模型(面向过程的风格):就像C一样,一条语句接一条语句的执行或者函数跳转;

抽象数据类型模型(基于对象的风格):仅仅使用了 class 的封装,很多人都是在用基于对象的风格却误以为自己在使用面向对象的风格;

面向对象模型(面向对象的风格):使用了 class 的封装和多态的编程思维(多态才是真正的面向对象的特征)。

不同范式的混用,可能会导致一些不好的后果。比如说,虽然你可以直接或间接处理继承体系中的一个base class object(抽象数据类型模型的良好行为),但只有通过pointer或reference的间接处理,才支持OO程序设计所需的多态性质(面向对象模型的行为)。

注:编译器层面,一个reference通常是以一个指针来实现的。

(2)多态的用途:一个接口,多种实现

(3)C++支持多态的方式:

经由一组隐式的转化操作

shape* ps = new circle(); //将派生类指针转化为指向基类的指针

经由virtual function机制

ps->rotate(); //rotate()为虚函数
(*ps).rotate();

经由dynamic_cast和typeid运算符

if(circle *pc = dynamic_cast<circle*>(ps))...

(4)一个对象的内存布局大小

  • 其nonstatic data member的总和大小;
  • 任何由于位对齐所需要的填补上去的空间,字节对齐一方面是为了不同硬件平台间的兼容,因为各个硬件平台对存储空间的处理不尽相同;另一方面是为了使bus的”运输量“达到最高效率,因为bus是以字节块为单位运送数据的,字节对齐可以避免一个数据被放到2个字节块中,从而需要多执行一次内存访问。
  • 加上了为了支持virtual机制而引起的额外负担。

(5)指针的类型

”指向不同类型的指针“间的差异,实际上在于它所寻址出来的对象的类型不同。而指针本身占据的内存空间是一定的,在32位机器上占用4个字节,64位机器上为8个字节。

”指针类型“会教导编译器如何解释某个特定地址中的内存内容和大小。换句话说就是,虽然基类指针和派生类指针指向同一个位置,但是类型的不同使得它们所涵盖的范围不同。在单继承的情况下,使用基类指针指向派生类对象,在执行期间,我们明确了指针指向的对象类型,即指针所涵盖的地址范围,所以我们可以利用该指针操作派生类的成员。如果需要调用virtual function,则需要通过该对象对应的vptr找到该类的vtbl,然后调用virtual function。

多态只能由”指针“或”引用“来实现,根本原因在于:
它们并不引发内存中任何”与类型有关的内存委托操作“。会受到改变的,只有它们所指向的内存的”大小和内容解释方式“而已。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
稳健回归(Robust Regression)是一种针对数据异常值影响的回归分析方法。相比于传统的最小二乘法,稳健回归能够忽略掉数据中的异常值,从而得到更加可靠的回归结果。 C++中可以使用类来实现稳健回归算法,主要包括以下步骤: 1. 定义类RobustRegression,包含以下成员变量和成员函数: 成员变量: - vector<double> x:自变量 - vector<double> y:因变量 - double b0:截距 - double b1:斜率 - int n:样本量 - double w:迭代权重 成员函数: - 构造函数RobustRegression(vector<double>& x, vector<double>& y):初始化自变量和因变量 - double getB0():获取截距 - double getB1():获取斜率 - double getR():获取相关系数 - void fit():拟合回归模型 2. 在构造函数中初始化自变量和因变量。在拟合回归模型时,使用Huber估计量来计算迭代权重。具体实现如下: ``` void RobustRegression::fit() { double delta = 1.345 * median(abs(x - median(x))); // Huber阈值 vector<double> residuals(n); // 残差 double w_sum = 0; // 权重和 for (int i = 0; i < n; i++) { residuals[i] = y[i] - (b0 + b1 * x[i]); // 计算残差 if (abs(residuals[i]) <= delta) { w = 1; // 权重为1 } else { w = delta / abs(residuals[i]); // 权重为delta/|residuals| } w_sum += w; // 累加权重 b0 += w * residuals[i]; // 更新截距 b1 += w * residuals[i] * x[i]; // 更新斜率 } b0 /= w_sum; // 计算截距的加权平均值 b1 /= w_sum; // 计算斜率的加权平均值 } ``` 3. 在类中实现获取截距、斜率和相关系数的成员函数。其中相关系数可以使用Pearson相关系数来计算: ``` double RobustRegression::getR() { double x_sum = 0, y_sum = 0, xy_sum = 0, x2_sum = 0, y2_sum = 0; for (int i = 0; i < n; i++) { x_sum += x[i]; y_sum += y[i]; xy_sum += x[i] * y[i]; x2_sum += x[i] * x[i]; y2_sum += y[i] * y[i]; } double r = (n * xy_sum - x_sum * y_sum) / sqrt((n * x2_sum - x_sum * x_sum) * (n * y2_sum - y_sum * y_sum)); return r; } ``` 4. 编写一个测试程序,读入自变量和因变量数据,创建RobustRegression对象并拟合回归模型,输出截距、斜率和相关系数。 完整的C++类实现代码如下: ``` #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; class RobustRegression { public: RobustRegression(vector<double>& x, vector<double>& y) : x(x), y(y), b0(0), b1(0), n(x.size()), w(0) {} double getB0() { return b0; } double getB1() { return b1; } double getR(); void fit(); private: vector<double> x; vector<double> y; double b0; double b1; int n; double w; }; void RobustRegression::fit() { double delta = 1.345 * median(abs(x - median(x))); // Huber阈值 vector<double> residuals(n); // 残差 double w_sum = 0; // 权重和 for (int i = 0; i < n; i++) { residuals[i] = y[i] - (b0 + b1 * x[i]); // 计算残差 if (abs(residuals[i]) <= delta) { w = 1; // 权重为1 } else { w = delta / abs(residuals[i]); // 权重为delta/|residuals| } w_sum += w; // 累加权重 b0 += w * residuals[i]; // 更新截距 b1 += w * residuals[i] * x[i]; // 更新斜率 } b0 /= w_sum; // 计算截距的加权平均值 b1 /= w_sum; // 计算斜率的加权平均值 } double RobustRegression::getR() { double x_sum = 0, y_sum = 0, xy_sum = 0, x2_sum = 0, y2_sum = 0; for (int i = 0; i < n; i++) { x_sum += x[i]; y_sum += y[i]; xy_sum += x[i] * y[i]; x2_sum += x[i] * x[i]; y2_sum += y[i] * y[i]; } double r = (n * xy_sum - x_sum * y_sum) / sqrt((n * x2_sum - x_sum * x_sum) * (n * y2_sum - y_sum * y_sum)); return r; } int main() { vector<double> x = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; vector<double> y = {2.1, 4.2, 6.3, 8.4, 10.5, 12.6, 14.7, 16.8, 18.9, 21}; RobustRegression rr(x, y); rr.fit(); cout << "b0 = " << rr.getB0() << endl; cout << "b1 = " << rr.getB1() << endl; cout << "r = " << rr.getR() << endl; return 0; } ``` 上述程序中,自变量和因变量数据都是手动输入的。在实际应用中,可以从文件或数据库中读取数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值