对象模型-关于对象

Nonstattic data members 被置于每个class object 之中, static data members则被存放在个别class object 之外, static 和 nonstatic function members也被放在个别class object之外

对于虚函数virtual function 则以两个步骤支持:

  • 每个class产生出一堆指向 virtual function 的指针,被放在表格之中, 这个表格被称为virtual table(vtbl)

  • 每个class object 被安插一个指针,指向这个表格,这个指针被称为vptr, vptr的setting和resetting都由每个class的constructor、destructor、copy assignment运算符 自动完成,每个class所关联的type_info object 也经由virtual table被指出来,通常放在表格的第一个slot

单个类


#include "stdafx.h"
#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
	}
	virtual ~Base()
	{
		cout << "Base::~Base" << endl;
	}
	virtual void BaseTest()
	{
		cout << "Base::BaseTest" << endl;
	}
	virtual void BaseTest1()
	{
		cout << "Base::BaseTest1" << endl;
	}
};

int main()
{
	{
		Base ObjBase;
		cout << sizeof(ObjBase) << endl;
	}

	system("pause");
	return 0;
}

输出:4

我们去掉virtual关键字,输出:1

当我们增加virtual关键字,当创建类对象时会创建一个虚函数表指针

若监视ObjBase变量会发现,虚函数表有3个函数指针

第1个元素: Base::~Base
第2个元素: Base::BaseTest
第2个元素: Base::BaseTest1

单继承


class Derived1 : public Base
{
public:
	virtual ~Derived1()
	{
		cout << "Derived1::~Derived1" << endl;
	}
	virtual void BaseTest()
	{
		cout << "Derived1::BaseTest" << endl;
	}
	virtual void DerivedTest()
	{
		cout << "Derived1::DerivedTest" << endl;
	}
public:
	int m_nTest;
};
{
	Derived1 *pDerived1 = new Derived1;

	delete pDerived1;
}

上面的例子子类Derived1以公有继承的方式继承基类Base, 也可以制定虚拟继承即:virtual public Base, 在虚拟继承的方式下,base class不管在继承体系中被派生多少次, base class 永远只会存在一个实例(base class suboject), 这个属性在如今的体验好的互联网产品中的应用起到非常关键的作用

以上面代码为例,创建了子类Derived1指针指向子类Derived1本身, 子类Derived1只重写了BaseTest函数, 此时我们监视pDerived1变量,会发现子类Derived1的实例模塑出了 基类Base的实例,虚函数表在模塑出的基类Base实例之下,虽然虚函数表在 模塑出的基类Base实例之下,但是发现析构函数和BaseTest函数的指针指向子类 Derived1的析构函数和BaseTest函数。

这种继承模型(public inheritance)

优点:

  • 对于基类Base class members最紧凑而且最有效率的存取

缺点:

  • 对于基类Base class members的任何改变(增加、移除、改变类型) 都使得所有用到base class 或者 derived class 的实例必须重新编译

当父类有虚函数时,子类继承父类的虚函数表,而且虚函数的顺序是先父类的虚函数,再子类的虚函数; 当父类的虚函数被子类重写时,则虚函数表中的父类虚函数指针要替换为子类的虚函数指针

此时我们监视pDerived1变量,虚函数表有3个函数指针

第1个:指向Derived1::~Derived1
第2个:指向Derived1::BaseTest
第3个:指向Base::BaseTest1

对象的差异


C++程序设计支持三种程序设计范式

  1. 程序模型

    像C一样

  2. 抽象数据类型模型(ADT)

    对数据的表达通过一组公有接口,不支持类型的扩充。

  3. 面向对象模型

    此模型有共同的类型、共同的行为通过base class被封装起来

ADT设计范式可能比OO设计范式速度快而且空间紧凑。速度快是因为所有函数调用操作都在编译时期解析完成, 对象的构建也不需要设置virtual机制;而空间紧凑是因为每个object不需要负担传统上为了支持virtual机制而需要的额外负荷。 但是ADT设计范式相比OO设计范式比较没有弹性

纯粹以一种上述的范式编写,有助于整体行为的稳固,如果混合了不同的范式, 就可能带来意想不到的结果。例如以一个具体的base class 实例来完成多态而不是通过base class的指针pointer或 引用reference来完成多态。

以上面代码为例:

{
	// 产生意想不到结果
	Base objBase;
	Derived1 objDerived1;
	objBase = objDerived1;
	objBase.BaseTest();
}

{
	// 正确的多态姿势
	Derived1 objDerived1;
	Base &objBase1 = objDerived1;
	objBase1.BaseTest();
}

在OO设计范式下,程序员需处理一个未知实例,虽然它的类型有所界定,但是有无穷可能,类型受限于继承体系。 理论上没有深度、广度的限制,被指定的object的真实类型在每一个特定执行点之前是无法被解析的,相反的是抽象数据类型 范式中,程序员的处理是一个拥有单一类型的实例,编译期间就已定义好

对于object的多态操作,要求此object必须可以经由一个指针pointer或引用reference来存取, 但是C++中的pointer或reference的处理却不是多态的必要结果

例如:

// 没有多态
int *pTmp;
// 没有语言支持的多态
void *pObject;

在C++,多态只存在于public class体系中,基类指针可以指向基类的object,也可以指向根据public继承关系派生而来的一个 子类型,nopublic的继承行为以及类型为void*的指针可以说是多态,但它们并没有被语言明确的支持,也是就是说必须由程序员通过显示的 转换的操作来管理

C++以下列方法支持多态

  • 隐式转换
  • 基类指针指向子类

  • 经由virtual function机制

  • 经由dynamic_cast 和type_id运算符

指针类型


例如一个指向整数的指针和一个指向类对象的指针有什么不同?

指针的内存需求没什么不同,都需要相同的空间存储指针

只是在寻址出来的object类型上不同,也就是说指针类型会教导编译器如何解释某个特定地址中的内存内容及其大小

例如一个指向地址为1000的类型为void*的指针,涵盖了怎样的地址空间,我们并不知道

这就是为什么一个类型为void的指针只能够持有一个地址,而不能通过它操作所指object的缘故*

转换(cast)是一种编译器指令,只影响被指出的内存的大小及其内容

以上面代码为例:

{
	Derived1 objDerived1;
	Base *pBase = &objDerived1;
	Derived1 *pDerived1 = &objDerived1;
}

那么pBase和pDerived1有什么不同?

相同:pBase和pDerived1都指向objDerived1的第一个byte

不同:pDerived1所涵盖的地址包含整个Derived1 object,而pBase所涵盖的地址只包含Derived1 object中的基类Base subobject

除了Base subobject中出现的member,我们不能通过pBase来处理Derived1中的member。唯一例外的是通过virtual机制。

例如我们使用这篇文章static_cast和dynamic_cast提到的转换手法。

Derived1* pTmp = static_cast<Derived1*>(pBase);
cout << pTmp->m_nTest << endl;

或者:

if (Derived1* pTmp = dynamic_cast<Derived1*>(pBase))
{
	cout << pTmp->m_nTest << endl;
}

引用上面的一个例子:

{
	// 产生意想不到结果
	Base objBase;
	Derived1 objDerived1;
	objBase = objDerived1;
	objBase.BaseTest();
}

调用objBase.BaseTest()时输出: Base::BaseTest

这也说明了多态的实现并不是发挥在直接存取object上, OO程序设计的多态需通过base class的指针pointer或引用reference来完成 而一个pointer或者一个reference之所以能够支持多态,是因为它们并不引发内存中任何与类型有关的内存委托操作, 会受到的只是上面提到的它们所指向的内存的“大小和内容的解释方式

同时,关于objBase = objDerived1编译器在初始化及指定之间做出决断,编译器必须确保如果某个object含有一个或以上的vptrs, 而vptrs的内容不会被base class初始化或改变,当一个子类object赋值给基类object时,子类object就会被切割以塞入较小的基类内存中。

最后


C++通过class的pointer和reference来支持多态,这种程序设计风格称为“面向对象”