C++对象模型——"无继承"情况下的对象构造(第五章)
来源:互联网 发布:百度一下淘宝商城 编辑:程序博客网 时间:2024/06/07 02:35
5.1 "无继承"情况下的对象构造
考虑下面这个程序片段:1Point global;23Point foobar()4{5Point local;6Point *heap = new Point;7*heap = local;8// ... stuff ...9delete heap;10return local;11}L1,L5,L6表现出三种不同的对象产生方式:global内存配置,local内存配置和heap内存配置.L7把一个 class object指定给另一个,L10设定返回值,L9则明确地以 delete 运算符删除heap object.
一个object的生命是,该object的一个执行周期,local object的生命从L5的定义开始,到L10为止.global object的生命和整个程序的生命相同.heap object的生命从它被 new 运算符配置出来开始,到它被 delete 运算符摧毁为止.
下面是Point的第一次声明,可以写成C程序.C++ Standard说这是一种所谓的Plain Of Data声明形式:
typedef struct {float x, y, z;} Point;如果以C++来编译这段码,会发生什么事情?观念上,编译器会为Point声明一个trivial default constructor,一个trivial destructor,一个trivial copy constructor,以及一个trivial copy assignment operator.但实际上编译器会分析整个声明,并为它贴上Plain Of Data标签.
当编译器遇到这样的定义:
1 Point global;时,观念上Point的trivial constructor和destructor都会被产生并被调用,constructor在程序起始(startup)处被调用而destructor在程序的exit()处被调用(exit()由系统产生,放在main()结束之前).然而,事实上那些trivial members要不是没被定义,就是没被调用,程序的行为一如它在C中的表现一样.
在C中,global被视为一个"临时性的定义",因为它没有明确的初始化操作,一个"临时性的定义"可以在程序中发生多次.那些实例会被链接器折叠起来,只留下单独一个实体,被放在程序data segment中一个"特别保留给为初始化的global objects使用"的空间.
C++并不支持"临时性的定义",这是因为 class 构造行为的隐含应用的缘故.global在C++中被视为完全定义.C++中所有全局对象都被当作"初始化过的数据"来对待.
foobar()函数中的L5,有一个Point object local,同样也是既没有被构造也没有被解构.Point object local如果没有先经过初始化,可能会成为潜在的程序bug——万一第一次使用它就需要其初始值的话(如L7).至于heap object在L6的初始化操作:
Point *heap = new Point;会被转换为对 new 运算符(由library提供)的调用:
Point *heap = __new(sizeof(Point));再一次强调,并没有default constructor施行于 new 运算符所传回的Point object上.L7对此object有一个赋值(赋值,assign)操作,如果local曾被适当地初始化过,一切就没有问题.
7 *heap = local;事实上这一行会产生编译器警告如下:
warning,line 7: local is used before being initialized.观念上,这样的指定操作会触发trivial copy assignment operator进行拷贝搬运操作.然而实际上此object是一个Plain Of Data,所以赋值操作(assignment)将只是像C那样的纯粹位搬运操作,L9执行一个 delete 操作:
9 delete heap;会被转换为对 delete 运算符(由libraray提供)的调用:
__delete(heap);观念上,这样的操作会触发Point的trivial destructor.但destructor要不是没有被产生就是没有被调用.最后,函数以传值(by value)的方式将local当作返回值传回,这在观念上会触发trivial copy constructor,不过实际上 extern 操作只是一个简单的位拷贝操作,因为对象是一个Plain Of Data.
抽象数据类型 (Abstract Data Type)
以下是Point的第二次声明,在 public 接口下多了 private 数据,提供完整的封装性,但没有提供任何 virtual function:class Point {public:Point(float x = 0.0, float y = 0.0 float z = 0.0): _x(x), _y(y), _z(z) {}// no copy constructor, copy operator or destructor defined ...private:float _x, _y, _z;};这个经过封装的Point class,其大小并没有改变,还是三个连续的float,是的,不论 private,public 存取层,或是member function的声明,都不会占用额外的对象空间.
没有为Point定义一个copy constructor或copy operator,因为默认的位语意(default bitwise semantics,第二章51页)已经足够了.也不需要提供一个destructor,因为程序默认的内存管理方法也足够.
对于一个global实体:
Point global;// 实施Point::Point(0.0, 0.0, 0.0);现在有了default constructor作用于其上,由于global被定义在全局范畴中,其初始化操作将延迟到程序激活(startup)时才开始(详见6.1节)
如果要对 class 中的所有成员都设定常量初值,那么给予一个 explicit initialization list会比较高效.甚至在local scope中也是如此.举个例子,考虑下面这个程序片段:
void mumble() {Point local1 = {1.0, 1.0, 1.0};Point local2;// 相当于一个inline expansion// explicit initialization 会稍微快一些local2._x = 1.0;local2._y = 1.0;local2._z = 1.0;}local1的初始化操作会比local2的高效,因为当函数的activation recored被放进程序堆栈时,上述initialization list中的常量就可以被放进local1内存中.
explicit initialization list带来三项缺点:
1.只有当 class members都是 public 时,此方法才奏效.
2.只能指定常量,因为它们在编译时期就可以被评估求值.
3.由于编译器并没有自动施行,所以初始化行为的失败可能会比较高一些.
explicit initialization list所带来的效率优点能够补偿其软件工程上的缺点吗?
一般而言,答案是no.然而在某些特殊情况下又不一样.例如,或许以手工打造了一些巨大的数据结构如调色盘,或者正要把一堆常量数据倾倒给程序,那么explicit initialization list的效率会比 inline constructor好的多,特别是对全局对象而言.
在编译器层面,会有一个优化机制用来识别 inline constructor,后者简单地提供一个member-by-member的常量指定操作,然后编译器会抽取那些值,并且对待它们就好像是 explicit initialization list所供应的一样,而不会把constructor扩展成一系列的assignment指令.
于是,local Point object的定义:
{Point local;}现在被附加上default Point constructor的 inline expansion:
{// inline expansion of default constructorPoint local;local._x = 0.0;local._y = 0.0;local._z = 0.0;}L6配置出一个heap Point object:
6Point *heap = new Point;现在则被附加一个"对default Point constructor的有条件调用操作":
Point *heap = __new(sizeof(Point));if (heap != 0)heap->Point::Point();然后才又被编译器进行 inline expansion操作,至于把heap指针指向local object:
7*heap = local;则保持这简单的位拷贝操作,以传值方式传回local object,情况也是一样:
10return local;L9删除heap所指的对象:
9delete heap;该操作并不会导致destructor被调用,因为并没有明确地提供一个destructor函数实体.
观念上,Point class 有一个相关的default copy constructor,copy operator和destructor,然而它们都是无关紧要的,而且编译器实际上根本没有产生它们.
为继承做准备
第三个Point声明,将为"继承性质"以及某些操作的动态决议(dynamic resolution)做准备,当限制对z成员进行存取操作:class Point {public:Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) {}// no destructor, copy constructor, or copy operator defined ...virtual float z();protected:float _x, _y;};再一次强调,并没有定义一个copy constructor,copy operator,destructor.所有的members都以数值来存取,因此在程序层面的默认语意的下,行为良好.
virtual functions的导入促使每一个Point object拥有一个 virtual table Pointer.这个指针提供 virtual 接口的弹性,代价是:每一个object需要一个额外的一个word空间.有什么重大影响吗?视应用情况而定!必须视它对多态设计所带来的实际效益的比例而定.
除了每一个 class object多负担一个vptr之外,virtual function的引入也引发编译器对Point class 产生膨胀作用:
定义的constructor被附加了一些码,以便将vptr初始化.这些码必须被附加在任何base class constructor的调用之后,但必须在任何由使用者(程序员)供应的代码之前.例如,下面就是可能的附加结果:
Point *Point::Point(Point *this, float x, float y) : _x(x), _y(y) {// 设定object的virtual table pointer(vptr)this->__vptr_point = __vtbl__point;// 扩展member initialization listthis->_x = x;this->_y = y;// 传回this对象return this;}合成一个copy constructor和一个copy assignment operator,而且其操作不再是trivial(但implicit destructor仍然是trivial).如果一个Point object被初始化或以一个derived class object赋值,那么以位为基础(bitwise)操作可能给vptr带来非法设定.
// copy constructor的内部合成inline Point* Point::Point(Point *this, const Point &rhs) {// 设定object的virtual table pointer(vptr)this->__vptr_Point = __vtbl__Point;// 将rhs坐标中的连续位拷贝到this对象,或是经由member assignment提供一个member ...return this;}编译器在优化状态可能会把object的连续内容拷贝到另一个object上,而不会实现一个精确地"以成员为基础"的赋值操作.C++ Standard要求编译器尽量延迟nontrivial memebrs的实际合成操作,直到真正遇到其使用场合为止.
1Point global;23Point foobar()4{5Point local;6Point *heap = new Point;7*heap = local;8// ... stuff ...9delete heap;10return local;11}L1的global初始化操作,L6的heap初始化操作以及L9的heap删除操作,都还是和稍早的Point版本相同,然而L7的memberwise赋值操作:
*heap = local;很可能触发copy assignment operator的合成,以及其调用操作的一个 inline expansion(内部扩展);以 this 取代heap而以rhs取代local.
最戏剧性的冲击发生在以传值方式传回local的那一行(L10).由于copy constructor的出现,foobar()很可能被转化为下面这样(详见2.3节):
// C++伪代码:foobar()的转化,用以支持copy constructorPoint foobar(Point &__result) {Point local;local.Point::Point(0.0, 0.0);// heap的部分与前面相同...// copy constructor的应用__result.Point::Point(local);// local对象的destructor将在这里执行调用Point定义的destructor:local.Point::~Point();return;}如果支持named return value(NRV)优化,这个函数会进一步被转化为:
// C++伪代码:foobar()的转化// 以支持named return value(NRV)优化Point foobar(Point &__result) {__result.Point::Point(0.0, 0.0);// heap的部分与前面相同...return;}
一般而言,如果设计中有许多函数都需要以传值方式(by value)传回一个local class object,例如像如下形式的一个算数运算:
</pre><pre name="code" class="cpp">T operator+(const T &, const T &) {T result;// ...真正的工作在此return result;}那么提供一个copy constructor就比较合理--甚至即使default memberwise语意已经足够.它的出现会触发NRV优化.然而,就像前一个例子中所展现的那样,NRV优化后将不再需要调用copy constructor,因为运算结果已经被直接置于"将被传回的object"体内了.
0 0
- C++对象模型——"无继承"情况下的对象构造(第五章)
- C++对象模型——继承体系下的对象构造(第五章)
- 5.1 无继承情况下的对象构造
- C++对象模型——构造,解构,拷贝语意学(第五章)
- 5.2 继承体系下的对象构造
- [读书笔记] 深入探索C++对象模型-第五章-构造、析构、拷贝语义学(下)
- 当用new创建对象时(无继承和有继承两种情况下)虚拟机的执行过程
- C++对象模型 第五章 构造、析构、拷贝语意学
- Java知识点整理:第五章:类的声明,构造方法,方法,继承中的构造器,对象实例化过程
- C++对象模型——对象的构造和解构(第六章)
- C++构造(创建)对象的多种情况
- C++对象模型——解构语意学(第五章)
- javascript面向对象编程——构造函数的继承
- 深度探索c++对象模型之虚继承的对象构造
- objective c下的对象模型
- C++ — 关于菱形虚拟继承对象模型的探究
- 多重继承的对象模型
- 继承的对象模型整理
- Android中对日期进行排序
- 用Properties加载“ .properties "文件产生的java.lang.NullPointerException的解决之道
- DoubleLinkedList独立实现,不依赖任何包
- Problem E: 二维数组中的查找
- java虚拟机
- C++对象模型——"无继承"情况下的对象构造(第五章)
- Web安全测试之跨站请求伪造(CSRF)
- eclipse编译数学函数库
- nginx的数据结构集合(随时更新)
- Min Stack
- 黑马学习笔记_Java 反射技术
- 【读书笔记】iOS-截屏功能的实现。
- linux scp permission denied
- Problem K: 二进制表示中1的个数