深度探索C++对象模型(1):关于对象(C和C++的区别)

时间:2020-12-22 01:22:33


关于对象

  • 加上封装后的布局成本
  • C++对象模型
  • 简单对象模型
  • 表格驱动型模型
  • C++对象模型
  • 对象模型如何影响程序
  • 关键词所带来的差异
  • 关键词的困扰
  • 对象的差异
  • 指针的类型
  • 加上多态之后


在C语言中,“数据”和“处理数据的操作(函数)是分开声明的”,也就是说,语言本身并没有支持“数据和函数”之间的关联系,我们把这种程序方法称为程序性的,由一组“分布在各个一功能为导向的函数中”的算法所驱动,他们处理的是共同的外部数据,如

// t.c
int func(int);		// 函数声明
int func(int n) {
	return n + 1;
}
int main(int argc, char* argv[], char** env[]) {
	int n = 1;		// 数据声明
	int p = func(n);
	return 0;
}

而在C++中是这样的

class {
	int n;
	int func();
};

本章主要是介绍了C和C++的区别,不做过多概述了

加上封装后的布局成本

上述C++的封装相对于C增加了多少成本?答案是没有增加成本。

数据成员直接内含在每一个类对象中,就像C的struct一样。

#include <iostream>
using namespace std;
class A {
public:
    int n;
    A(int m): n(m) {}
    void func() {
        cout << "a" << endl;
    }
};
int main() {
    A a(2);
    cout << sizeof(a) << endl;
    return 0;
}
// 4,此处的4字节就是类实例对象的int变量的实例化占用空间

成员函数虽然含在类的声明之中,却并不出现在类之中
成员函数一般都是定义在代码区的

用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间。按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分配存储单元,如下图所示。

深度探索C++对象模型(1):关于对象(C和C++的区别)


能否只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。如下图所示。

深度探索C++对象模型(1):关于对象(C和C++的区别)


显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间。

inline和非inline区别
需要说明,不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储。不要将成员函数的这种存储方式和inline(内联)函数的概念混淆。不要误以为用inline声明(或默认为inline)的成员函数,其代码段占用对象的存储空间,而不用inline声明的成员函数,其代码段不占用对象的存储空间。不论是否用inline声明(或默认为inline),成员函数的代码段都不占用对象的存储空间。用inline声明的作用是在调用该函数时,将函数的代码段复制插人到函数调用点,而若不用inline声明,在调用该函数时,流程转去函数代码段的入口地址,在执行完该函数代码段后,流程返回函数调用点。inline与成员函数是否占用对象的存储空间无关,它们不属于同一个问題,不应搞混。

C++对象模型

在C++中,有两种类数据成员:静态的(static)和非静态的(nostatic),以及三种类成员函数:静态的,非静态的,虚拟的

简单对象模型

以“指向member的指针”放在对象中,可以避免“member有不同的类型,因而需要不同的存储类型”。

可能是用指针更消耗内存,也没应用到实际产品上,就不做概述啦。

表格驱动型模型

放两个指针,一个指向数据成员表,一个指向数据函数表。

没有实际应用,不做概述啦。

C++对象模型

在此模型中,非静态数据成员被配置与每一个类对象中,静态数据成员则被存放在个别的类对象之外静态和非静态成员函数也被放在个别的类对象之外

virtual function则以两个步骤支持之:

  • 每一个类产出一堆指向virtual functions的指针,放在表格之中,这个表格被称为virtual table(vtbl)
  • 每一个类对象被安插一个指针,指向相关的vtbl,通常这个指针被称为vptr(vptr的设定和重置都由每一个类的构造函数、析构函数和拷贝赋值构造运算符共同完成)

每一个类所关联的type_info 对象(RTTI)也经由vtbl被指出来,通常放在表格的第一个slot

深度探索C++对象模型(1):关于对象(C和C++的区别)


多重继承角度来看

//间接基类A
class A{
protected:
    int m_a;
};

//直接基类B
class B: virtual public A{  //虚继承
protected:
    int m_b;
};

//直接基类C
class C: virtual public A{  //虚继承
protected:
    int m_c;
};

//派生类D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正确
    void setb(int b){ m_b = b; }  //正确
    void setc(int c){ m_c = c; }  //正确
    void setd(int d){ m_d = d; }  //正确
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

深度探索C++对象模型(1):关于对象(C和C++的区别)

对象模型如何影响程序

不同的对象模型会导致“现有的程序代码必须修改”以及“必须加入新的程序代码”两个结果。

关键词所带来的差异

这主要是C和C++的,有点好玩,建议去看书,比如

int (*pf)(1024);

这是一个函数调用还是一个函数声明?

关键词的困扰

struct的默认访问权限是public,class可以自定义访问权限

其他讲解的都是概念性的东西,在此不做讲解啦

对象的差异

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

  • 程序模型:如数组
  • 抽象数据类型模型(ADT):如string,vector
  • 面向对象模型:class

指针的类型

一个指向类对象的指针和一个指向整数的指针有什么不同?以内存需求来看,没什么不同;其不同在于会教导编译器如何结合某个特定的地址中的内存内容及其大小。

所以,转换(cast)其实是一种编译器指令。大部分情况下他并不改变一个指针所含的真正地址,他只影响“被指出之内存的大小和其内容”的解释方式

加上多态之后

有些鸡肋,还是写一下吧。

OO和OP
OO:oriented process,面向过程
OP:oriented object,面向对象

先来看一段代码

class B {
	virtual void pp();
	...
};
class A: public B {
	void rotate();
	virtual void pp();
	...
};
// 第一种
A a();
A *aa = &a;
A &aaa = *aa;
// 第二种
A a();
B *ab = &a;
A &aa = *aa;

其中aa和ab都指向A对象的第一个字节,其间的差别是,a所涵盖的地址包含整个A对象,而ab所涵盖的地址只包含A中的B子类。除了B子类出现的成员,你不能使用ab来直接处理A的任何成员

ab->pp()		// 正确
ab->rotate()		// 错误

参考博客和文档
一、C++成员函数在内存中的存储方式 二、生成一个C++对象的成本
三、虚基类和虚拟继承一点通
四、class和struct区别