5.1 "无继承"情况下的对象构造

考虑以下这个程序片段:

  1. 1 Point global;
  2. 2
  3. 3 Point foobar()
  4. 4 {
  5. 5 Point local;
  6. 6 Point *heap = new Point;
  7. 7 *heap = local;
  8. 8 // ... stuff ...
  9. 9 delete heap;
  10. 10 return local;
  11. 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声明形式:

  1. typedef struct {
  2. float x, y, z;
  3. } Point;

假设以C++来编译这段码,会发生什么事情?观念上,编译器会为Point声明一个trivial default constructor,一个trivial destructor,一个trivial copy constructor,以及一个trivial copy assignment operator.但实际上编译器会分析整个声明,并为它贴上Plain Of Data标签.

    当编译器遇到这种定义:

  1. 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的初始化操作:

  1. Point *heap = new Point;

会被转换为对 new 运算符(由library提供)的调用:

  1. Point *heap = __new(sizeof(Point));

再一次强调,并没有default constructor施行于 new 运算符所传回的Point object上.L7对此object有一个赋值(赋值,assign)操作,假设local曾被适当地初始化过,一切就没有问题.

  1. 7 *heap = local;

其实这一行会产生编译器警告例如以下:

  1. warning,line 7: local is used before being initialized.

观念上,这种指定操作会触发trivial copy assignment operator进行拷贝搬运操作.然而实际上此object是一个Plain Of Data,所以赋值操作(assignment)将仅仅是像C那样的纯粹位搬运操作,L9运行一个 delete 操作:

  1. 9 delete heap;

会被转换为对 delete 运算符(由libraray提供)的调用:

  1. __delete(heap);

观念上,这种操作会触发Point的trivial destructor.但destructor要不是没有被产生就是没有被调用.最后,函数以传值(by value)的方式将local当作返回值传回,这在观念上会触发trivial copy constructor,只是实际上 extern 操作仅仅是一个简单的位拷贝操作,由于对象是一个Plain Of Data.

抽象数据类型 (Abstract Data Type)

下面是Point的第二次声明,在 public 接口下多了 private 数据,提供完整的封装性,但没有提供不论什么 virtual function:

  1. class Point {
  2. public:
  3. Point(float x = 0.0, float y = 0.0 float z = 0.0)
  4. : _x(x), _y(y), _z(z) {}
  5. // no copy constructor, copy operator or destructor defined ...
  6. private:
  7. float _x, _y, _z;
  8. };

这个经过封装的Point class,其大小并没有改变,还是三个连续的float,是的,不论 private,public 存取层,或是member function的声明,都不会占用额外的对象空间.

    没有为Point定义一个copy constructor或copy operator,由于默认的位语意(default bitwise semantics,第二章51页)已经足够了.也不须要提供一个destructor,由于程序默认的内存管理方法也足够.

    对于一个global实体:

  1. Point global; // 实施Point::Point(0.0, 0.0, 0.0);

如今有了default constructor作用于其上,因为global被定义在全局范畴中,其初始化操作将延迟到程序激活(startup)时才開始(详见6.1节)

    假设要对 class 中的全部成员都设定常量初值,那么给予一个 explicit initialization list会比較高效.甚至在local scope中也是如此.举个样例,考虑以下这个程序片段:

  1. void mumble() {
  2. Point local1 = {1.0, 1.0, 1.0};
  3. Point local2;
  4. // 相当于一个inline expansion
  5. // explicit initialization 会略微快一些
  6. local2._x = 1.0;
  7. local2._y = 1.0;
  8. local2._z = 1.0;
  9. }

local1的初始化操作会比local2的高效,由于当函数的activation recored被放进程序堆栈时,上述initialization list中的常量就能够被放进local1内存中.

    explicit initialization list带来三项缺点:

    1.仅仅有当 class members都是 public 时,此方法才奏效.

    2.仅仅能指定常量,由于它们在编译时期就能够被评估求值.

    3.因为编译器并没有自己主动施行,所以初始化行为的失败可能会比較高一些.

    explicit initialization list所带来的效率长处可以补偿其软件project上的缺点吗?



    一般而言,答案是no.然而在某些特殊情况下又不一样.比如,也许以手工打造了一些巨大的数据结构如调色盘,或者正要把一堆常量数据倾倒给程序,那么explicit initialization list的效率会比 inline constructor好的多,特别是对全局对象而言.

    在编译器层面,会有一个优化机制用来识别 inline constructor,后者简单地提供一个member-by-member的常量指定操作,然后编译器会抽取那些值,而且对待它们就好像是 explicit initialization list所供应的一样,而不会把constructor扩展成一系列的assignment指令.

    于是,local Point object的定义:

  1. {
  2. Point local;
  3. }

如今被附加上default Point constructor的 inline expansion:

  1. {
  2. // inline expansion of default constructor
  3. Point local;
  4. local._x = 0.0;local._y = 0.0;local._z = 0.0;
  5. }

L6配置出一个heap Point object:

  1. 6 Point *heap = new Point;

如今则被附加一个"对default Point constructor的有条件调用操作":

  1. Point *heap = __new(sizeof(Point));
  2. if (heap != 0)
  3. heap->Point::Point();

然后才又被编译器进行 inline expansion操作,至于把heap指针指向local object:

  1. 7 *heap = local;

则保持这简单的位拷贝操作,以传值方式传回local object,情况也是一样:

  1. 10 return local;

L9删除heap所指的对象:

  1. 9 delete heap;

该操作并不会导致destructor被调用,由于并没有明白地提供一个destructor函数实体.

    观念上,Point class 有一个相关的default copy constructor,copy operator和destructor,然而它们都是无关紧要的,并且编译器实际上根本没有产生它们.

为继承做准备

第三个Point声明,将为"继承性质"以及某些操作的动态决议(dynamic resolution)做准备,当限制对z成员进行存取操作:

  1. class Point {
  2. public:
  3. Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) {}
  4. // no destructor, copy constructor, or copy operator defined ...
  5. virtual float z();
  6. protected:
  7. float _x, _y;
  8. };

再一次强调,并未定义一个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的调用之后,但必须在不论什么由使用者(程序猿)供应的代码之前.比如,以下就是可能的附加结果:

  1. Point *Point::Point(Point *this, float x, float y) : _x(x), _y(y) {
  2. // 设定object的virtual table pointer(vptr)
  3. this->__vptr_point = __vtbl__point;
  4. // 扩展member initialization list
  5. this->_x = x;
  6. this->_y = y;
  7. // 传回this对象
  8. return this;
  9. }

合成一个copy constructor和一个copy assignment operator,并且其操作不再是trivial(但implicit destructor仍然是trivial).假设一个Point object被初始化或以一个derived class object赋值,那么以位为基础(bitwise)操作可能给vptr带来非法设定.

  1. // copy constructor的内部合成
  2. inline Point* Point::Point(Point *this, const Point &rhs) {
  3. // 设定object的virtual table pointer(vptr)
  4. this->__vptr_Point = __vtbl__Point;
  5. // 将rhs坐标中的连续位复制到this对象,或是经由member assignment提供一个member ...
  6. return this;
  7. }

编译器在优化状态可能会把object的连续内容复制到还有一个object上,而不会实现一个精确地"以成员为基础"的赋值操作.C++ Standard要求编译器尽量延迟nontrivial memebrs的实际合成操作,直到真正遇到其使用场合为止.

  1. 1 Point global;
  2. 2
  3. 3 Point foobar()
  4. 4 {
  5. 5 Point local;
  6. 6 Point *heap = new Point;
  7. 7 *heap = local;
  8. 8 // ... stuff ...
  9. 9 delete heap;
  10. 10 return local;
  11. 11 }

L1的global初始化操作,L6的heap初始化操作以及L9的heap删除操作,都还是和稍早的Point版本号同样,然而L7的memberwise赋值操作:

  1. *heap = local;

非常可能触发copy assignment operator的合成,以及其调用操作的一个 inline expansion(内部扩展);以 this 代替heap而以rhs代替local.

    最戏剧性的冲击发生在以传值方式传回local的那一行(L10).因为copy constructor的出现,foobar()非常可能被转化为以下这样(详见2.3节):

  1. // C++伪代码:foobar()的转化,用以支持copy constructor
  2. Point foobar(Point &__result) {
  3. Point local;
  4. local.Point::Point(0.0, 0.0);
  5. // heap的部分与前面同样...
  6. // copy constructor的应用
  7. __result.Point::Point(local);
  8. // local对象的destructor将在这里运行调用Point定义的destructor:local.Point::~Point();
  9. return;
  10. }

假设支持named return value(NRV)优化,这个函数会进一步被转化为:

  1. // C++伪代码:foobar()的转化
  2. // 以支持named return value(NRV)优化
  3. Point foobar(Point &__result) {
  4. __result.Point::Point(0.0, 0.0);
  5. // heap的部分与前面同样...
  6. return;
  7. }
    一般而言,假设设计中有很多函数都须要以传值方式(by value)传回一个local class object,比如像例如以下形式的一个算数运算:
  1. </pre><pre name="code" class="cpp">T operator+(const T &, const T &) {
  2. T result;
  3. // ...真正的工作在此
  4. return result;
  5. }

那么提供一个copy constructor就比較合理--甚至即使default memberwise语意已经足够.它的出现会触发NRV优化.然而,就像前一个样例中所展现的那样,NRV优化后将不再须要调用copy constructor,由于运算结果已经被直接置于"将被传回的object"体内了.

C++对象模型——&quot;无继承&quot;情况下的对象构造(第五章)的更多相关文章

  1. C++对象模型——继承体系下的对象构造(第五章)

    5.2 继承体系下的对象构造 当定义一个object例如以下: T object; 时,实际上会发生什么事情呢?假设T有一个constructor(不论是由user提供或是由编译器合成),它会被调用. ...

  2. linux 无外网情况下安装 mysql

    由于工作需要,需要在一台装有 CentOS 系统的测试服务器上安装 MySQL ,由于该服务器上存有其他比较重要的测试数据,所以不能连接外网.由于之前安装 MySQL 一直都是使用 yum 命令一键搞 ...

  3. Tomcat 程序无问题的情况下页面打开变慢的原因

    看看这写日志的频率就知道我有多闲了.. 前言: 其实关于tomcat,遇到过很多关于“慢”的问题,比如启动慢,比如页面打开慢, 以前太忙也太懒,不愿意花时间分析原因,现在终于肯静下来找原因 环境是ec ...

  4. centos在无外网情况下,进行yum挂载

  5. ORACLE不使用工具的情况下获取对象DDL

    set line 200set pagesize 0set long 99999set feedback offset echo off获得表.索引.视图.存储过程.函数的DDL:select dbm ...

  6. C++对象在继承情况下的内存布局

    1,C++ 中继承是非常重要的一个特性,本节课研究在继承的情形下,C++ 的对象模 型又有什么不同: 2,继承对象模型(最简单的情况下): 1,在 C++ 编译器的内部类可以理解为结构体: 2,子类是 ...

  7. Oracle 无备份情况下的恢复--临时文件/在线重做日志/ORA-00205

    13.5 恢复临时文件 临时文件没有也不应该备份.通过V$TEMPFILE可以找到所有的临时文件. 此类文件的损坏会造成需要使用临时表空间的命令执行失败,不至于造成实例崩溃或session中断.由于临 ...

  8. C++ 构造函数的执行过程(一) 无继承

      引言 C++ 构造函数的执行过程(一) 无继承 本篇介绍了在无继承情况下, C++构造函数的执行过程, 即成员变量的构建先于函数体的执行, 初始化列表的数量和顺序并不对构造函数执行顺序造成任何影响 ...

  9. Android热修复技术选型(不在市场发布新版本的情况下,直接更新app)

    2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker,它们在原理各有不同,适用场景各异,到 ...

随机推荐

  1. CentOS 7下ElasticSearch集群搭建案例

    最近在网上看到很多ElasticSearch集群的搭建方法,本人在这人使用Elasticsearch5.0.1版本,介绍如何搭建ElasticSearch集群并安装head插件和其他插件安装方法. 一 ...

  2. java设计模式之策略模式总结

    策略模式的定义:(定义截自http://www.cnblogs.com/whgk/p/6087064.html) 1.策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立 ...

  3. centos7服务器安装fail2ban配合Firewalld防护墙防止SSH爆破与防护网站CC攻击

    centos7服务器安装fail2ban配合Firewalld防护墙防止SSH爆破与防护网站CC攻击 1.检查firewalld是否启用 #如果您已经安装iptables建议先关闭 service i ...

  4. 梦想CAD控件安卓控件事件

    MxDrawActivity.commandEvent 命令调用事件. 参数 说明 int iCommand 命令ID,这个ID用户自已来取的,只要多个命令ID不重复就可以 代码实现如下: publi ...

  5. Object.prototype 原型和原型链

    Object.prototype 原型和原型链 原型 Javascript中所有的对象都是Object的实例,并继承Object.prototype的属性和方法,有些属性是隐藏的.换句话说,在对象创建 ...

  6. Number 数据类型

    //Number 数据类型//包含 整数 小数 NaN(not a number)var a = 1233;var b = 12.34;//1/'a'//把其他数据类型转化成数字,他在转化时,只要字符 ...

  7. ZOJ - 3983 - Crusaders Quest(思维 + 暴力)

    题意: 给出一个字符串,长度为9,包含三种各三个字母"a","g","o",如果一次消除连续三个一样的分数+1,消完自动向左补齐 其中可以消 ...

  8. cin的返回对象

    //有时间回来补坑 //先记上几个有用的博客 https://blog.csdn.net/candj/article/details/4419585https://www.cnblogs.com/gy ...

  9. 你相信吗??Python把数字也当做对象!@@@对象,名称绑定,引用计数

    本文学习自:http://blog.csdn.net/yockie/article/details/8474408 1.对象 Python中, 万物皆对象,包括12345等int常量.不信吗??用di ...

  10. python使用xlrd和xlwt读写Excel文件

    版权声明:本文为博主原创文章,未经允许不得转载. 安装模块 如果使用的是Linux系统,并且安装了pip,可以直接使用pip安装xlrd, xlwt: pip install xlwt pip ins ...