首页 / C++ / C++对象模型——C++对象
C++对象模型——C++对象
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了C++对象模型——C++对象,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含10976字,纯文字阅读大概需要16分钟。
内容图文
![C++对象模型——C++对象](/upload/InfoBanner/zyjiaocheng/603/1fcb58ddf4f8401a81f302e3acc4321a.jpg)
对象
C语言的魅力在于它的精瘦和简易——开发快,执行快
C++呢?封装,继承,多态到底带来了什么好处?——软件工程学
1 封装
将数据和操作封装在一起
封装的成本之没啥成本
//C语言,表示一个三维空间的点
//C语言,数据和操作分开,函数处理共同的外部数据
typedef struct point3d
{
float x;
float y;
float z;
} Point3d;
//C++封装,表示一个三维空间的点
//数据和操作封装在一起,函数处理内部的数据
class Point3d
{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0) : _x(x), _y(y), _z(z) {}
float x() { return _x; }
float y() { return _y; }
float z() { return _z; }
void x(float xval) { _x = xval; }
void y(float yval) { _y = yval; }
void z(float zval) { _z = zval; }
private:
float _x;
float _y;
float _z;
};
答案:以上封装没有增加任何成本。
三个数据成员内含在每一个对象之中。对于C语言,没有给Point3d变量也是内含这3个数据成员。
每一个非内联函数只会诞生一个函数实例。对于C语言,每个函数只会诞生一个实例。
封装的成本之成本在virtual
C++的空间成本和时间成本主要是有virtual引起的:
(1)virtual function机制:用以支持一个有效率的“执行期绑定”
(2)virtual base class:用以实现“多次出现在继承体系中的bass class,有一个单一而被共享的实例”
2 C++对象模式——对象如何存储
1、class data members(类数据成员):static和nonstatic。static被所有实例所共享,所以存储空间应该只有一个;nonstatic,每个实例对象都内含了所有的nonstatic。
2、class member functions(类函数成员):static、nonstatic、virtual。
(1)static被所有实例对象所共享,不需要隐式的指针来调用,可以使用类+作用域直接调用,有点像全局函数。static函数由于没有指针,那么其中操作的数据成员必然也只能是静态的数据成员,而不能直接操作非静态的数据成员。
(2)nonstatic有隐式的对象指针指出函数操作的是哪个对象的数据成员
(3)virtual通过指针指出最终调用的是那个派生类的函数,既然需要指针说明virtual和static是不能作用于同一个函数声明的
C++的编译器是如何解释,存储这些类成员的???
//C++是如何表示每一个对象的这些
//数据成员:静态,非静态成员
//函数成员:虚函数,静态函数,非静态函数
class Point {
public:
Point(float xval);
virtual ~Point(); //虚函数
float x() const; //非静态函数
static int PointCount(); //静态函数
protected:
virtual ostream& print(ostream &os) const; //虚函数
float _x; //非静态数据成员
static int _point_count; //静态数据成员
}
C++对象模型之简单对象模型
简单是对于C++编译器来说实现起来简单。同时它的理解也确实不难。但是最终并没有在编译器上实现。
一个object是一系列的slots,每一个slot指向一个members。
members按其声明顺序,各被指定一个slot
每一个对象并不存放成员本身,而是存放“指向member的指针”,这样可以避免“members有不同的类型,因而需要不同的存储空间”的所带来的问题。
但是并没有编译器实现这种对象模型。
不过“指向成员的指针”这一概念被应用到了最终的对象模型实现中。
C++对象模型之表格驱动的对象模型
为了对所有的classes的所有的objects都有一致的表达方式(好大的野心),该模型把所有的members相关的信息抽出来,放在一个data member table和一个member function table之中,class object本身则内含指向这两个表格的指针。
PS:
member function table是一系列的slots,每一个slot指向要给member function
data member table则直接持有data本身
这个方案中成员函数表的思想成为了支持virtual functions的一个有效方案。
但是这个对象模型最终并没有实际应用到C++上。——为啥,感觉这思想挺叼的。既然有成员函数表,那么它应该也支持多态,为啥不用这个对象模型呢?
数据成员的访问效率低了?再看看后面的吧。
(1)提供了一层间接性,当应用程序代码本身未曾改变,但是所用到的class objects的nonstatic data members有所改变时,并不需要重新编译应用程序代码,因为对于应用程序代码其中的对象只包含了两个指针,指针这样一层间接性屏蔽了对象本身的数据成员的变化。
(2)而对于函数成员,其在应用程序中都是地址,如果增加或删除了函数成员,对于表格驱动模型和C++对象模型(实际应用的模型)来说,都需要重新编译应用程序。
(3)但是也由于这样的间接层,表格驱动模型付出了空间和执行效率两方面的代价
总结
1、简单对象模型
数据成员和函数成员在对象实例中表现形式并无差别,都是指针,指向成员所在的实际地址。
2、表格驱动的对象模型
对象持有两个指针,分别指向数据成员表和函数成员表。
(1)数据成员表直接持有数据本身
(2)函数成员表内含函数地址
3 C++对象模型
Stroustrup(本贾尼. 斯特劳斯特卢普,大佬)当初设计的C++对象模型是从简单对象模型派生而来的,并对内存空间和存取时间做了优化。
1、模型特点:
(1)nonstatic data members被配置于每一个class object之内
(2)static data members被存放在个别的class object之外
(3)static和nonstatic也被放在个别的class object之外
(4)virtual funcitons的实现——每个类一个虚表;每个对象一个虚表指针
a、每一个class产生出一堆指向virtual functions的指针,放在表格之中。称为virtual table(虚表)
b、每一个class object被安插一个指针,指向相关的virtual table(虚表)。
虚表指针的设定和重置由每一个类的构造,析构,赋值运算符自动完成。
每一个class所关联的type_info object也经由virtual table被指出来,通常放在表格的第一个slot。——没搞懂
4 C++对象模型之继承
简单对象模型之继承
首先类本身只是一种数据格式规范,它在声明后是不会被分配内存的,它只是高速编译器该如何去解析声明为该类的实例化对象
每一个base class(基类)可以被derived class object(派生类)内的一个slot指出,
该slot内含base class subobject(基类子对象)地址。
派生类对象存储的都是指针,分别指向基类对象,数据成员,函数成员。
优点:class object(类对象)的大小不会因其base classes(基类)的改变而受到影响。
缺点:间接型(指向基类对象的指针)导致空间和存取时间上的额外负担。
基类表之继承
base table模型,base class table被产生出来时,表格中每一个slot内含一个相关的base class地址。
每一个class object内含一个指针,指向其base class table。
派生类生成一个基类表,派生类对象内含一个指向该表的指针。
优点:每一个class object中对于继承都有一致的表现方式——一个指向基表的指针,与基类的大小或个数无关。
无需改变class objects本身,就可以放大、缩小或更改base class table
缺点:间接性导致的空间和存取时间上的额外负担
对于多重继承,要想到达最顶层,则需要多次间接存取才能探取到结果。
C++之继承模型
C++最初采用的继承模型并不运用任何间接性:base class subobject的data members被直接放置于derived class object中。
缺点:base class members的任何改变(增、删,改类型等),都是的所用到“此base class或derived class之objects者”必须重新编译。
virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针。
其他演化出来的模型则要不是导入一个virtual base class table,就是扩充原已存在的virtual table,以便维护每一个virtual base class的位置。
5 对象模型如何影响程序
如前面所描述的表格模型,当基类增删改时,对应用程序本身没有影响,不需要编译。
不同的对象模型带来的影响——两个结果:
(1)现有的程序代码必须修改
(2)必须加入新的程序代码
我觉得这里我的理解为不同的对象模型,使得编译器解释代码的行为有所不同;面对类的变化,对应用程序的影响。
书中提到的这段代码信息量很大,我在此把它再写一遍,我很喜欢这一段代码
//class X定义了一个copy constructor,一个virtual destructor和一个virtual function foo()
X foobar()
{
X xx;
X *px = new X;
// foo()是一个virtual function
xx.foo();
px->foo();
delete px;
return xx;
}
//这个函数有可能在内部被转换为:
void foobar(X& _result)
{
//构造_result
//_result用来取代local xx
_result.X::X(); //以引用入参的形式取代返回值
//对函数的调用也用上了类名 + 作用域符
//扩展 X *px = new X
px = _new(sizeof(X)); //先分配空间,再构造
if (px != 0)
px->X::X(); //这里对非虚函数都采用了这种类名 + 作用域符的调用方式
//这里的指针应该是非静态成员函数的隐藏参数
//扩展xx.foo(),但不使用virtual机制
//以_result取代xx
foo(&_result); //这里也可以使用virtual机制,因为每一个对象本身是带有虚函数表指针的
//传输的参数也指示也此次调用的是哪个foo()
//使用virtual机制扩展px->foo()
(*px->vtbl[2])(px); //使用虚表中的函数地址实现调用,大爱
//vtbl,这个成员在我们编码的时候对程序员是不可见的
//但是在调试的时候,我们可以看到vtbl以及其中的函数
//C++编译器背着我们做了很多事情。
//扩展delete px
if (px != 0)
{
(*px->vtbl[1])(px);
_delete(px);
}
return;
}
6 对象的差异
C++程序设计模型直接支持三种programming paradigms(程序设计范式)
(1)过程式模型(procedutal model)
(2)抽象数据类型模型(ADT)——封装本身并不具有多态行为,它的类型在编译期就确定了
(3)面向对象模型(object-oriented model)
在C++中,只有通过pointers和references的操作才能实现多态(延迟,未来)。C++的多态只存在于一个个的public class体系中。
C++支持多态的方式:
(1)通过一组隐式的转换操作。shape *ps = new circle(); //把派生类对象的指针赋给基类。(从对象模型来说,指针指向对象内存空间的起始位置,这种复制方式是把派生类对象所包含的基类无名对象的地址赋给它吗?)
(2)通过虚函数机制。
(3)通过dynamic_cast和typeid运算符。dynamic_cast把基类对象指针转换成派生类对象指针;typid运算符返回数据类型或表达式的具体信息(对于对于类类型的数据,类型信息是指对象所属的类、所包含的成员、所在的继承关系等)。这两个运算符结合在一起就可以实现类继承体系中的向下转换。
C++类对象的内存
一个class object需要多少内存:
(1)nonstatic data members的总和大小
(2)边界对齐所需要的填充字节
(3)为了支持virtual而由内部产生的负担
7 指针的类型
不同类型指针的差异——编译器解释地址空间的方式不同:
由“指针类型”教导编译器如何解释某个特定地址中的内容及其大小。
(1)对于一个指向地址1000的整数指针,在32位上,将涵盖地址空间1000~1003。
(2)而一个指向类类型的指针,编译器会按照该类的大小size去解释它,将连续的大小为size的内存中数据解释为该类类型。
(3)对于一个类型为void*的指针,涵盖的地址空间是未知的。
(4)转换(cast)其实是一种编译指令——它只影响 ”被指出之内存的大小和其内容“ 的解释方式。(这种转换不一定要是dynamic_cast,const_cast,static_cast这种的显示强制类型转换)
多态中的指针
指针的类型影响了编译器对其所指地址空间的 “大小和内容” 的解释方式。——即对于一个指向派生类对象的基类指针,不能使用它来处理派生类的任何members。唯一例外的是virtual机制(因为虚表的存在)。
而当用一个派生类去初始化一个基类对象时,编译器会在初始化以及指定操作(将一个class object指定给另一个class object)之间做出仲裁。
编译器必须确保如果一个object含有一个或一个以上的vptrs,那些vptrs的内容不会被base class object初始化或改变。
8 OB和OO之间的取舍
OB——object-based,基于对象的编程,即ADT,重视数据的封装。相比于对等的OO,其设计速度更快而且空间更紧凑(没有间接层,所有操作在编译时期完成)。但是OB设计比较没有弹性。
OO——object-oriented,面向对象的变成。通过class的pointers和referencesl来支持多态。弹性大,但是开发周期和效率相比OB长。
先了解清楚OB和OO的行为和应用领域的需求,才能在两者之间做出有效的选择。
实验
1、多重继承,最底层的类对象大小
(1)基类非静态数据总和
(2)虚表指针——如果存在多继承,则可能内含多个虚表指针
(2)虚基类——一个虚基类就增加4字节的开销
2、同时具有虚基类,虚函数的类对象的大小
一个虚函数表占据一个4个字节的地址。
一个虚基类占据一个4个字节的地址——经过测试多继承中,如果继承 n 个虚基类,则增加了了 4*n 字节的虚基类地址开销。
3、具有虚函数的类的虚表有基类的虚函数吗
没有。只有自己的虚函数。
4、对于多继承来说,基类指针指向派生类,不同基类指针的值是相同的吗?还是说指向派生类对象中的无名子对象。
(1)对于多继承的派生类对象,有几个基类就有几个虚表指针(基类具有虚函数),而虚表指针所指向的虚表中存放的是派生类的虚函数。
(2)基类指针会指向派生类对象中对应类型的无名基类对象。并把地址空间的大小和内容解释为基类类型。
5、把派生类对象赋给基类对象,基类对象所指向的虚表和派生类相比有什么不同
(1)把派生类对象赋给基类对象,此时基类对象中所包含的虚表指针指向的虚表中包含的是基类的虚函数。
(2)派生类对象中的虚表指针存放在基类无名对象中,而且虚表中存放的是派生类的虚函数。
(3)虽然派生类中对应的基类无名对象中存放着虚表指针指向的虚函数表中存放的是派生类虚函数,但是在把派生类对象赋给基类对象后,基类对象中的虚表指针指向的虚函数表中存放的是基类的虚函数。
内容总结
以上是互联网集市为您收集整理的C++对象模型——C++对象全部内容,希望文章能够帮你解决C++对象模型——C++对象所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。