5.3    对象复制语意学 (Object Copy Semantics)

当设计一个 class,并以一个 class object指定给 class object时,有三种选择:

    1.什么都不做,因此得以实施默认行为.

    2.提供一个 explicit copy assignment operator.

    3.明白地拒绝一个 class object指定给还有一个 class object.

    假设要选择第3点,不同意将一个 class object指定给还有一个 class object,那么仅仅要将copy assignment operator声明为 private,而且不提供其定义就可以.把它设置为 private,就不再同意于不论什么地点(除了在member functions以及此 class 的friend中)进行赋值(assign)操作.不提供其函数定义,则一旦某个member
function或 friend 企图影响一份拷贝,程序在链接时就会失败
.一般觉得这和链接器的性质有关.

    在这一节,验证copy assignment operator的语意,以及它们怎样被模塑出来,利用Point class 来帮助讨论:

class Point {
public:
Point(float x= 0.0, float y = 0.0);
// ... 没有virtual function
protected:
float _x, _y;
};

没有理由须要禁止拷贝一个Point object.因此问题就变成了:默认行为是否足够?假设要支持的仅仅是一个简单的拷贝操作,那么默认行为不但足够并且有效率,没有利用再自己提供一个copy assignment operator.

    仅仅有在默认行为导致的语意不安全或者不对时,才须要设计一个copy assignment operator.默认的memberwise copy行为对Point不安全吗?不对吗?

不,因为坐标都内带数值,所以不会发生"别名话"或"内存泄露".假设自己提供copy assignment operator,程序反倒会运行的比較慢.

    假设不正确Point供应一个copy assignment operator,而仅仅是依赖默认的memberwise copy,编译器会产生出一个实体吗?这个答案和copy constructor的情况一样:实际上不会!因为此 class 已经有了bitwise copy语意,所以implicit copy assignment operator被视为毫无用处,也根本不会被合成出来.

    一个 class 对于默认的copy assignment operator,在下面情况不会表现出bitwise copy语意:

    1.当 class 内带一个member object,而其 class 有一个copy assignment operator时.

    2.当一个 class 的base class 有一个copy assignment operator时.

    3.当一个 class 声明了不论什么 virtual functions(一定不可以拷贝右端 class object的vptr地址,由于它可能是一个derived class object).

    4.当 class 继承自一个 virtual base class(不论此base class 有没有copy operator)时.

    C++ Standard上说copy assignment operators并不表示bitwise copy semantics是nontrivial.实际上,仅仅有nontrivial instances才会被合成出来.

    于是,对于Point class,这种赋值(assign)操作:

Point a, b;
a = b;

由bitwise copy完毕,把Point b拷贝给Point a,其间并没有copy assignment operator被调用.从语意上或从效率上考虑,这都是所须要的.注意,还是可能提供一个copy constructor,为的是把name return value(NRV)优化打开.copy constructor的出现不应该暗示出也一定要提供一个copy assignment operator.

    如今导入一个copy assignment operator,用以说明该operator在继承之下的行为:

inline Point &Point::operator=(const Point &p) {
_x = p._x;
_y = p._y;
}

如今派生出一个Point3d class(请注意是虚拟继承):

class Point3d : virtual public Point {
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0);
protected:
float _z;
};

假设没有为Point3d定义一个copy assignment operator,编译器就必须合成一个,合成而得的东西可能看起来像这样:

// C++伪代码:被合成的copy assignment operator
inline Point3d &Point3d::operator=(Point3d *const this, const Point3d &p) {
// 调用base class的函数实体
this->Point::operator=(p);
// memberwise copy the derived class members
_z = p._z;
return *this;
}

copy assignment operator有一个非正交性情况(nonorthogonal aspect,意指不够理想,不够严谨的情况),那就是它缺乏一个member assignment list.因此不能重写:

// C++伪代码,下面性质并不支持
inline Point3d &Point3d::operator=(const Point3d &p3d) : Point(p3d), z(p3d._z)
{}

必须写成下面两种形式,才干调用base class 的copy assignment operator:

Point::operator=(p3d);

(*(Point *)this) = p3d;

缺少copy assignment list,看起来也许仅仅是一件小事,但假设没有它,编译器一般而言就没有办法压抑上一层base class 的copy operators被调用.比如,以下是个Vertex copy operator,当中Vertex也是虚拟继承自Point:

// class Vertex : virtual public Point
inline Vertex &Vertex::operator=(const Vertex &v) {
this->Point::operator(v);
_next = v._next;
return *this;
}

如今从Point3d和Vertex中派生出Vertex3d,以下是Vertex3d的copy assignment operator:

inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {
this->Point::operator=(v);
this->Point3d::operator(v);
this->Vertex::operator=(v);
}

编译器怎样可以在Point3d和Vertex的copy assignment operators中压抑Point的copy assignment operators呢?编译器不可以反复传统的constructor解决方式(附加上额外的參数).这是由于,和constructor以及destructor不同的是,"取copy assignment operator地址"的操作是合法的.因此,以下这个样例是合法程序代码(尽管它也推翻了希望把copy assignment operator做的更静止的企图):

typedef Point3d &(Point3d::*pmfPoint3d) (const Point3d &);
pmfPoint3d pmf = &Point3d::operator=;
(x.*pmf)(x);

    //看不懂.................................

    然而无法支持它,仍然须要依据其独特的继承体系,插入不论什么可能数目的參数给copy assignment operator.这一点在支持由 class object(内带 virtual base class)所组成的数组的配置操作时,也被证明是非常有问题的(详见6.2节)

    还有一个方法是,编译器可能为copy assignment operator产生分化函数(split functions),以支持这个 class 成为most-derived class 或成为中间的base class.假设copy assignment operator被编译器产生的话,那么"slit function解决方式"可说是定义明白.但假设它是被 class 设计者所完毕的,那就不算是定义明白.比如,怎样分化像以下这种函数(特别是当init_bases()为 virtual 时):

inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {
init_bases(v);
}

    其实,copy assignment operator在虚拟继承情况下行为不佳,须要特别小心地设计和说明.

    假设使用一个以语言为基础的解决方法,那么应该为copy assignment operator提供一个附加的"member copy list".简单地说,不论什么解决方式假设是以程序操作为基础,将导致较高的复杂度和较大的错误倾向.
一般公认,这是语言的一个弱点,也是应该小心检验程序代码的地方(当使用 virtual base classes时).

    有一种方法能够保证most-derived class 会引发 virtual base class subobject的copy行为,那就是在derived class 的copy assignment operator函数实体的最后,明白地调用那个operator,像这样:

inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {
this->Point3d::operator=(v);
this->Vertex:;operator=(v);
// must place this last if your compiler dose not suppress intermediate class invocations
this->Point::operator=(v);
}

这并不能省略subobjects的多重拷贝,但却能够保证语意正确.还有一个解决方式要求把 virtual subobjects复制到一个分离的函数中,并依据call path条件化调用它.

    最好的办法是尽可能不要同意一个 virtual base class 的拷贝操作.甚至有一个奇怪的方法是:不要在不论什么 virtual base class 中声明数据

C++对象模型——对象复制语意学 (Object Copy Semantics)(第五章)的更多相关文章

  1. es6对象复制合并 Object.assign

    对象的复制 var obj= { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); //{ a: 1 } 对象的合并和封装 v ...

  2. 【C++对象模型】第五章 构造、解构、拷贝 语意学

    1.构造语义学 C++的构造函数可能内带大量的隐藏码,因为编译器会扩充每一个构造函数,扩充程度视 class 的继承体系而定.一般而言编译器所做的扩充操作大约如下: 所有虚基类成员构造函数必须被调用, ...

  3. 【转】JavaScript中的对象复制(Object Clone)

    JavaScript中并没有直接提供对象复制(Object Clone)的方法.因此下面的代码中改变对象b的时候,也就改变了对象a. a = {k1:1, k2:2, k3:3}; b = a; b. ...

  4. C++对象模型(二):The Semantics of Copy Constructors(拷贝构造函数之编译背后的行为)

    本文是 Inside The C++ Object Model's Chapter 2  的部分读书笔记. 有三种情况,需要拷贝构造函数: 1)object直接为另外一个object的初始值 2)ob ...

  5. Object.assign() 从一个或多个源对象复制到目标对象

    Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象.它将返回目标对象. 1.语法: Object.assign(target, ... , sources) 参 ...

  6. 复制对象(一)copy和mutableCopy方法

    本文转载至 http://www.tuicool.com/articles/Fn6rMn CSDN博客原文  http://blog.csdn.net/u010962810/article/detai ...

  7. Object.assign()的用法 -- 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,返回目标对象

    语法: Object.assign(target, …sources) target: 目标对象,sources: 源对象用于将所有可枚举属性的值从一个或多个源对象复制到目标对象.它将返回目标对象. ...

  8. 深度探索C++对象模型之第二章:构造函数语意学之Copy constructor的构造操作

    C++ Standard将copy constructor分为trivial 和nontrivial两种:只有nontrivial的实例才会被合成于程序之中.决定一个copy constructor是 ...

  9. JS--bom对象:borswer object model浏览器对象模型

    bom对象:borswer object model浏览器对象模型 navigator获取客户机的信息(浏览器的信息) navigator.appName;获得浏览器的名称 window:窗口对象 a ...

随机推荐

  1. Oracle数据库初学者入门教程

    Oracle数据库是相对于其他数据库来说比较难的一个.Oracle Database,又名Oracle RDBMS,简称Oracle.是甲骨文公司推出的一款关系数据库管理系统.Oracle数据库系统是 ...

  2. 音频处理中的尺度--Bark尺度与Mel尺度

    由于人耳对声音的感知(如:频率.音调)是非线性的,为了对声音的感知进行度量,产生了一系列的尺度(如:十二平均律),这里重点说下Bark尺度与Mel尺度.刚开始的时候,我自己也没弄明白这两个尺度的区别. ...

  3. Centos7 时间不正确修复

    查看系统支持的时区列表 timedatectl list-timezones 使用 date -R 查看时区是否正确 date -R 修改时区 timedatectl set-timezone Asi ...

  4. Django 内容回顾

    模板 变量 {{ }} 标签 {% %} if elif else for empty forloop() with...as csrf_token 过滤器 default length add da ...

  5. BZOJ 3625 多项式求逆+多项式开根

    思路: RT //By SiriusRen #include <bits/stdc++.h> using namespace std; <<,mod=; int A[N],C[ ...

  6. WinForm窗体项目 之 MySchool管理系统终极版

    学习WinForm窗体程序也有一段时间了,今天就来尝试着来一个项目热热身~ 在我们通常使用的MySchool管理中,不外乎这几种功能:增.删.改.查.改密码 在过去的C#中确实是挺简单的,但是在学习了 ...

  7. Java引用jar的优化

    一般java的类文件开头都是各种引用: 如 上图的引用可以写成

  8. drupal 8——图片组(list)在前台的显示顺序在登录状态和非登录状态不同

    问题描述:该页面是通过view来输出的,然而,登录状态下其页面中的图片组输出顺序是乱序的,而非登录状态下则根据id值升序输出. 原因:在原view配置页面中,没有配置默认的排序字段 解决方案:在vie ...

  9. cookie范例

    GET /locate/api/getLocByIp?key=C6E22B7D480E3312C74EC7EF013E50C5&callback=bowlder.cb._0 HTTP/1.1 ...

  10. cocos creator游戏适配这事

    在想cocos适配之前,我们想想网页是怎么适配的.浏览器有各种规格,网页的一般做法是:背景图片铺满,网页内容保持在背景图片上居中,就实现了适应或者适配.css一般这样: .bg{ height:582 ...