inside the c++ object model中文版,
5.建构、解构、复制建构、解构、复制的语义
纯函数的存在性-纯VF的存在性
PureFunction可以定义和调用:只能静态调用,不能通过虚拟机调用;《出埃及记》内联void Abstract _ base:interface()const {.}抽象_基础:接口();
必须定义纯虚析构函数,每个派生类析构函数都会被编译器扩展,静态调用每个虚基类和上层基类的析构函数;遗漏基类毁灭者的定义会导致连锁故障;
继承系统中每个类对象的析构函数都会被调用;建议——不要将虚析构函数声明为纯析构函数;
虚拟规格的存在性
一般来说,将所有成员函数声明为虚函数,然后通过编译器的优化操作去掉不必要的虚调用,并不是一个好的设计思路。Ex优化。内联函数被破坏;
虚拟规格中常量的存在性
决定一个虚函数是否为const,需要考虑子类的函数调用(const引用,const指针)和派生实例修改数据成员的可能性;
重新考虑类的声明
公共:
虚拟~抽象_基础_更新();//不再是纯粹的虚拟
虚拟void接口()=0;//不再是常量
const char * mumble()const { return _ mumble;} //不是虚拟的
受保护:
abstract _ base _ update(char * PC=0);//添加带参数的构造函数
char * _ mumble
5.1没有继承的对象构造
在c中,全局作为一个临时定义,放在数据段的BBS空间中,以符号开始的块;c不支持临时定义,所有全局对象都被当作‘初始化数据’;
抽象数据类型
显式初始化列表更有效。点pt={1.0,1.0,1.0 };
三个缺点1)类成员必须是公共的;2)只能指定常量,因为它们可以在编译时求值;3)初始化失败的可能性大;
为继承做准备
随着函数的引入,每个对象都有一个vtbl ptr,它提供了虚拟接口的灵活性,代价是每个对象都需要一个额外的字空间;编译器还将对类进行扩展转换;
-Constructor增加了vptr的初始化,在基类构造函数之后,程序员代码之前调用;《出埃及记》this-_ _ vptr _ Point=_ _ vtbl _ _ Point;
-组合复制构造器和复制分配运算符。
如果遇到需要通过逐值传递来返回局部类对象的函数;《出埃及记》T运算符(const T,const T ) { T结果;返回结果;}提供复制构造函数是合理的。即使默认memberwise的语义足够,也能触发NRV优化名称返回值;
5.2继承制度下的对象构造
构造函数可能包含大量隐藏代码,编译器对每个构造函数进行扩展,扩展的程度取决于类的继承体系;
1)成员初始化列表中记录的数据成员初始化操作将按照成员声明的顺序放入构造函数中;2)如果有一个成员没有出现在成员初始化列表中,但是它有一个默认的构造函数,那么必须调用默认的构造函数;3)在此之前,类对象有虚表点,必须设置为初始值;4)在此之前,必须调用所有的上层基类构造函数,按照基类声明的顺序;5)在此之前,必须调用虚基类构造函数,从左到右,由深到浅;
类对象的析构函数以相反的顺序被调用并以相反的顺序被构造(子类-
继承虚拟继承
最底层的派生类负责构造共享基类的子对象;
Vptr初始化语义
构造函数的调用顺序是:从根到端自下而上,从里到外由内向外;
5.3对象复制语义对象复制语义
防止一个calss对象赋给另一个类对象:将复制赋值操作符声明为private,并将其定义为empty(成员函数和类的朋友可以调用private操作符,当他们试图影响复制时,程序在链接时会失败——这与链接器有关,并不是所有的都会失败.)
对于简单的类结构(例如点),如果我们不提供复制赋值运算符或复制构造函数,编译器就不会产生实体。因为类具有按位复制语义,隐式复制被认为是无用的(琐碎的);
对于默认的复制赋值运算符,将不会有按位复制。
1)当一个类有一个成员对象并且它的类有一个复制赋值操作符时;2)当一个类的基类有复制赋值操作符时;3)类声明任何虚函数时(派生类对象的vptr地址是相对的);4)当类从虚拟基类继承时;
c标准12.8节:我们不指定哪个表示虚拟基类的子对象应该被隐喻定义的复制赋值操作符赋值(赋值)一次以上;
建议:尽可能不允许虚拟基类的复制操作;其他建议:不要在任何虚拟基类中声明数据;
5.4对象的功能对象效率
按位复制的效率最高。一旦引入虚继承,class不再允许按位复制语义,合成的内联复制构造函数和复制赋值运算符大大降低了效率;
5.5解构破坏的语义
如果类没有定义析构函数,那么只有类中的成员对象或者calss自己的基类有析构函数,编译器才会自动合成,否则析构函数可能会被认为是多余的;
程序员定义的析构函数的扩展方式类似于构造函数的扩展方式,顺序相反。
1)如果对象中存在vptr,则重置相关的vtbl2)毁灭者的函数本身被执行,在程序员的代码被执行之前,vptr被复位;3)如果类拥有成员对象和析构函数,则按照与声明顺序相反的顺序调用;4)如果任何一个直接上层/非虚基类有析构函数,它们将以声明顺序的逆序被调用;5)如果任何一个虚基类都有析构函数,并且这个类在最后是最具衍生性的,那么它们将按照原构造的相反顺序被调用;
-第5节结束-
6运行时语义运行时语义
6.1对象的构建和解构
一般来说,对象会尽量放在被使用的程序段附近(只有在使用时才定义,如果程序之前返回,可以避免生成对象),这样可以省去不必要的生成和销毁对象的操作;
全局对象
静态初始化和内存释放操作;在全局使用main()之前构造,在main()结束之前销毁;
程序C中的所有全局对象都放在程序的数据段中。如果指定了值,对象会以此为初始值,否则对象配置的内容为0;(C不会自动设置初始值。在C语言中,全局对象只能通过常量表达式来设置。)
局部静态对象
构造和销毁只能调用一次;
对象数组
1)将内存分配给n个连续的对象;2)在N个对象上实现析构函数和析构函数;
void* vec_new(void* array,size_t elem_size,int elem_count,void(*构造函数)(void*),void(*析构函数)(void*,char))
void* vec_delete(void* array,size_t elem_size,int elem_count,void (*destructor)(void*,chhar))
《出埃及记》点结[10];vec_new(结,sizeof(点),10,点:点,0);//未定义销毁。
默认构造函数和数组
获得构造函数的地址后,不能变成内联;
6.2新增和删除操作员
int * pi=new int(5);1)通过new运算符配置内存:int * pi=_ _ new(sizeof(int));2)建立初值:* pi=5;
只有在内存配置成功后才应该进行初始化:int * piif(pi=_ _ new(sizeof(int)))* pi=5;删除操作符类似于if (pi!=0)_ _ delete(pi);
new操作符其实是用标准的C malloc()完成的,delete操作符也是用标准的C free()完成的;
point 3d * p _ array=new point 3d[10];指定新建时的大小;
删除时不指定大小:delete[]p _ array;寻找数组维数对删除操作符的效率有很大影响。当程序员不提供括号Ex。删除p _ array只有第一个元素会被析构,其他元素依然存在;
删除派生类的基类指针:Point * ptr=new Point 3d[10];删除[]ptr;//只会调用Point的析构函数;
放置运算符的语义新增
Operator new的指针必须指向同类型的类或者一个新的内存,足够容纳这种类型的对象;(派生类不匹配)
char * arena=new char[sizeof(point 2w)];或者Point2w * arena=new Point2w
一般来说,放置运算符new不支持多态性;
6.3临时物体
c标准允许编译器有完全的自由生成临时对象:‘在某些情况下由处理器生成临时对象是必要的,也是方便的。这种临时对象是由编译器定义的。临时对象的销毁是完整表达式全表达式求值过程中的最后一步,它导致临时对象的生成。(标准12.2)
任何包含表达式执行结果的临时对象都应该保留,直到对象初始化完成;
const char * progName version=progName Prog version;-字符串温度;运算符(temp,progName,Prog version);progNameVersion=tempString:运算符char *();在…之时String:~ String();//指向未定义的堆内存;
const字符串空间=“”;-字符串温度;在…之时String:String(“”);const字符串space=temp//当temp被销毁时,引用也将无效;
如果一个临时对象被绑定到一个引用,那么该对象将一直保留,直到被初始化的引用的生命结束,或者临时对象的生命范围结束;
临时物品的神话
反聚集分解(拆分临时对象并将其放入缓冲区)
-第6节结束-
7站在对象模型的尖端在对象模型的尖端
7.1模板
通用编程的基础(STL)也用于属性混合或互斥机制(线程同步控制)的参数化技术;
模板类类型
类型(常数类型t1,常数类型T2) {.}用法:min(1.0,2.0);//程序栏类型绑定为double,生成min()的程序文本实体(mangling name)
模板的实例当前是模板实例。
编译器不会响应模板类的声明,上面提到的静态数据成员和枚举还不可用;
枚举状态只能由模板Point类的实体访问或操作:Point float:Status s;//OK;点:状态s;//错误;即使这两种类型在抽象上是相同的;(将枚举从非模板基类中取出以避免多重复制)
point float:freeList;point double:freeList;两个自由列表实体;分别与float实例和double实例化点类相关联;
点浮动* ptr=0;没有程序行为发生;指向类对象的指针本身不是类对象,编译器不需要知道与类相关的成员数据或对象布局数据;
定点浮点ref=0;引用将真正拥有点的浮动实体;- Point float临时(float(0));const Point float ref=临时;0是整数,会转换成点浮点型,编译器会判断是否可行;
定点浮点原点;会导致模板类的出现,并产生float实例化的对象布局;
Point float *p=新的Point float具有pointtemplate的float实例;2)新运算符(取决于size _ t);3)默认构造函数;
两种策略:1)编译时函数出现在origin和P存在的文件中;2)链接时,编译器通过辅助工具重新激活,模板函数实体存储在一个文件中(存储位置);
模板中模板的错误报告
通常,编译器在通过实际参数实现模板声明之前,只对其执行有限的错误检查(语法);语法以外的错误只有在定义了特定的实体之后才会被发现;
模板内模板名称解析中的名称解析方法
1)区分模板声明范围定义模板的程序;2)模板实例化的范围有一个带有模板的程序;
sr0 . invariant();被调用的是foo (double)-1),而_ val是int类型,属于类型不会改变的模板类成员。函数的分辨率只与函数的原型签名有关,与返回值无关;foo的调用与模板参数无关,属于模板声明的范围。此作用域中只有一个foo()
sr0 . type _ dependent();Type,与模板参数相关,将决定_member的具体类型;属于模板实例的范围;在这个作用域下有两个foo()声明。因为_member的类型是int,所以调用foo(int)-2);
编译器必须保持两个作用域上下文:1)模板声明的作用域,集中在通用模板类上;2)模板即时iant的范围,集中于特定实体;编译器的解析算法必须决定哪个是合适的作用域,然后搜索合适的名称;
函数的现有成员是成员函数实例。
如果一个虚函数被实例化,它的出现点就紧跟在它的类的出现点之后
7.2异常处理异常处理
为了保持执行速度,编译器可以在编译时建立数据结构进行支持;程序会展开,编译器可以忽略这些结构,直到抛出异常;
为了保持程序的大小,编译器可以在执行时建立一个支持的数据结构;影响程序的速度,编译器只在必要的时候才构建那些数据结构;
异常处理快速审查
异常处理包括:1)一个抛出子句,它在程序的某个地方发送一个异常。引发的异常可以是内置类型或用户定义的类型;2)一个或多个catch子句,每个子句都是一个异常处理程序。处理某种类型的异常,并处理花括号内的程序;3)一个try部分,一系列语句,可能触发catch子句工作;
推送程序解栈,在函数被推离栈之前,会调用函数的局部类对象的析构函数;
支持异常处理
对于异常,编译器负责:1)检查抛出操作发生的函数;2)决定throw操作是否发生在try段;3)如果是,编译系统必须将异常类型与每个catch子句进行比较;4)如果一致,流程控制权交给catch子句;5)如果抛出不发生在try部分,或者没有catch子句匹配,系统必须a)销毁所有活动的本地对象;b)从堆栈中移除当前函数unwindc)转到程序堆栈中的下一个函数,重复步骤2)-5);
确定try部分中是否发生throw。
-- try部分之外的区域没有活动的本地对象;-try部分之外的区域具有要解构的活动局部对象;-try部分内的区域;
程序计数器范围表
将异常的类型与每个catch子句的类型进行比较。
类型描述符Type descriptor
当一个实际的对象在程序执行时被抛出。
异常将被生成并放置在相同形式的异常数据堆栈中;
catch子句中对异常对象的任何更改都是局部的。
7.3运行时类型识别运行时类型识别
Downcast的向下转换有潜在的危险;
类型安全向下转换确保安全的向下转换操作。
支持空间和时间上的类型安全向下转换负担——需要额外的空间来存储类型信息type information,通常是指向某个类型信息节点的指针;-需要额外的时间来决定运行时类型的类型;
类型安全动态强制转换确保了安全性的动态转换。
动态_ cast《出埃及记》if(Derived * pD=dynamic _ cast Derived *(pB)).其他.
参考文献不是要点。
将dynamic_cast运算符应用于类指针类型将得到true或false-如果返回真实地址,则意味着对象的动态类型被确认;-如果返回0,则表示没有指向任何对象;
Dynamic_cast应用于reference,这与指针用于非类型安全强制转换的情况不同;如果将引用设置为0,则会生成一个初始值为0的临时对象,并将引用设置为临时对象的别名;-如果引用确实引用了适当的派生类,将执行向下转换;-如果reference不是真正的派生类,则不能返回0,抛出bad _ casteexception
《出埃及记》try { Derived rD=dynamic _ cast Derived(rB);} catch {bad_cast} {.}
Typeid运算符
typeid运算符返回一个常量引用,例如,if (typeid (rd)==typeid (Rb))的类型为type_info。Equal equal运算符是一个重载函数:booltype _ info:operator==(const type _ info)const;
Type_info不仅用于多态类,还用于内置类型和用户自定义类型(非多态);在这些情况下,type_info对象是静态获得的,而不是在运行时获得的;
7.4效率和灵活性
动态共享库,动态共享库
在当前的C模型中,新lib的类对象发生了变化,类的大小和每个直接(或继承)成员的偏移量在编译时是固定的(虚拟继承成员除外)。这样提高了效率,但是在二进制层面,影响了灵活性。对象的布局改变了,程序必须重新编译;没有做到‘图书馆互不侵犯’;
共享内存
分布式,二进制级别的对象模型,与编程语言无关;
-第7节结束-
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。