对象复制语意学

当我们设计一个 class, 并以一个 class object 指定给另一个 class object 时, 我们有三种选择:
1. 什么也不做, 实施默认行为
2. 提供一个 explicit copy assignment operator
3. 明确拒绝把一个 class object 指定给另一个 class object.

如果选择到三点, 那么只需要将 copy assignment operator 声明为 private, 且不提供其定义( C++11 标准里可以使 copy assignment operator = delete) 这样我们就不能在任何场合(当然除了 member functions 以及 friends 之中) 进行赋值操作. 一旦某个 member function 或 friend 企图影响一份拷贝, 程序在链接时就会失败.
这一届主要是验证 copy assignment 的语意, 以及它们如何被模塑出来, 我们用 Point class 来讨论:

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

没有什么理由需要禁止拷贝一个 Point object. 那么问题就变成了: 默认行为是否足够?如果我们要支持的只是一个简单的拷贝操作, 那么默认行为不仅足够, 而且有效率.
只有在默认行为不够安全或不够正确时, 我们才需要自己设计一个 copy assignment operator. 怎么, 默认的 memberwise copy 行为不够安全吗? 不正确吗? 哦,不. 由于坐标内都自带数值, 所以不会发生 别名化(aliasing) 或 内存泄露(memory leak). 如果我们自己提供一个 copy assignment operator, 程序反而会执行得比较慢.
那么我们不对 Point 供应一个 copy assignment operator, 而光是依赖默认的 memberwise copy, 编译器会产生一个实体吗? 答案和 copy constructor 的情况一样: 实际不会! 因为此 class 已经有了 bitwise copy 语意, 所以 implicit copy assignment operator 被视为 trivial, 因此不会被合成出来.
一个 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 operator 并不表示 bitwise copy semantics 是 nontrivial, 实际上, 只有 nontrivial instances 才会被合成出来.
于是, 对于我们的 Point class, 这样的赋值操作:

Point a, b;
...
a = b;

由 bitwise copy 完成, 把 Point b 拷贝到 Point a, 期间没有 copy assignment operator 被调用. 从语意上或效率上来考虑, 这都是我们需要的. 注意, 我们还是可能提供一个 copy constructor, 为的是把 name return vlaue(NRV) 优化打开. copy constructor 的出现不应该让我们认为也一定要提供一个 copy assignment operator.
现在我导入一个 copy assignment operator, 用以说明该 operator 在继承之下的行为:

inline
Point&
Point::operator=(const Point &p)
{
    _x = p._x;
    _y = p._y;
    return *this;
}
//现在派生一个 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, 编译器就必须合成一个(因为之前的第二和第四项理由):

//可能的合成结果
inline Point3d&
Point3d::operator=(Point3d* const this, const Point3d &p)
{
    this->Point::operator=(p);

    //memberwise copy the derived class members
    _z = p._z;
    return *this;
}

copy assignment operator 有一个非正交的情况, 那就是缺乏一个 member assignment list. 因此我们不能够写:

//不支持
inline Point3d&
Point3d::operator=(const Point3d &p3d)
    : Point(p3d), z(p3d._z)
{}

我们必须调用以下两种形式, 才能调用 base class 的 copy assignment operato

Point::operator=(p3d);

(*(Point*)this) = p3d;

缺少 copy assignment list 看起来或许只是一件小事, 但如果没有它, 编译器一般而言就没有办法压抑上一层 base class 的 copy operators 被调用. 例如下面也是虚拟继承 Point 的 Vertex copy operator:

//class Vertex : virtrual 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 operator 中压抑 Point 的 copy assignment operator 呢? 编译器不能够重复传统的 constructor 解决方案(附加额外参数), 这是因为, 和 constructor 以及 destructor 不同的是, 取 copy assignment operator 地址的操作是合法的. 以下是合法的代码:

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

然而我们却无法支持它, 我们仍需要根据其独特的继承体系安插任何可能数目的参数给 copy assignment operator. 这一点在我们支持由 class objects(内带 virtual base clases) 所组成的数组的配置操作时, 也被证明是很有问题的.
另一个方法是, 编译器可能为 copy assignment operator 产生分化函数(split functions)以支持这个 class 成为 most-derived class 或成为中间的 base class. 如果copy assignment operator 被编译器产生的话, 那么 split function 解决方案可以说是定义明确的, 但如果是由 class 的设计者完成的, 那么就不能算定义明确了. 比如, 一个人如何分化下面这样的函数呢?

// init_bases() 为 virtual
inline Veretx3d&
Vertex3d::operator=(const Vertex3d &v)
{
    init_bases( v );
}

事实上, copy assignment operator 在虚拟继承的情况下行为不佳, 需要小心的设计和说明. 许多编译器甚至并不尝试取得正确的语意, 它们在每一个中间的 copy assignment operator 中调用每一个 base class instance, 于是造成 virtual base class copy assignment operator 的多个实体被调用. 目前许多的编译器都是如此, 你的是怎样呢? C+ Standard 又是怎么说的呢?
C++ Standard 也并没有规定那些代表 virtual base class 的 subobject 是否该被 implicit defined 的 copy assignment operator 指派(赋值, assign) 内容一次以上.
如果在语言的层面上解决这个问题, 那么应该为 copy assignment operator 提供一个附加的 member copy list. 简单地说, 任何一个解决方案如果是以程序操作为基础, 将导致较高的复杂度和较大的错误倾向. 一般公认, 这是语言的一大弱点, 也是一个人应该小心检验其程序代码的地方(当他使用 virtual base ckasses 时).
其实有一种方法可以保证 most-deirved class 会引发 virtual base class subobject 的 copy 行为, 那就是在 derived class 的copy assignment operator 函数实体的最后, 明确调用那个 operator, 像这样:

inline Vertex3d&
Vertex3d::operator=(const Vertexd &v)
{
    this->Point3d::operator=(v);
    this->Vertex::operator=(v);
    //must place this if your cpmlier does
    //not suppress intermediate invcations
    this->Point:operator=(v);
    ...
}

这并不能够省略 subobject 的多重拷贝, 但却可以保证语意正确. 另一个解决方案是把 virtual subobject 拷贝到一个分离的函数中, 并根据call path 条件化的调用它.
但还是建议尽可能不要允许一个 virtual base class 的拷贝操作. 甚至还有一个更为过分的建议: 不要在任何 virtual base class 中声明数据.

时间: 2024-10-25 19:47:03

对象复制语意学的相关文章

C++对象模型——对象复制语意学 (Object Copy Semantics)(第五章)

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,那么只要

对象复制、克隆、深度clone

-------------------------------------------------------------------------------- ------------------------------------------------- 知道Java对对象和基本的数据类型的处理是不一样的.和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的

PHP5的对象复制

今天用yii开发程序,一个bug改了一晚上,最后发现问题出在了对象复制机制上,PHP5之前的对象复制只需要$object_a = $object_b即可,但PHP5这样得到的是浅复制,及指针指向,并不是真正的数据复制,如改变$object_b的值,则$object_a也同样会发生变化,这时,需要令$object_a = clone $object_b,这样$object_a就不会跟随变化了. 但有一个问题,clone同样是PHP5才有的,因此,如果设计对象复制,则PHP5为分界版本,兼容问题需要

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

JavaScript中并没有直接提供对象复制(Object Clone)的方法.因此下面的代码中改变对象b的时候,也就改变了对象a. a = {k1:1, k2:2, k3:3}; b = a; b.k2 = 4; 如果只想改变b而保持a不变,就需要对对象a进行复制. 用jQuery进行对象复制 在可以使用jQuery的情况下,jQuery自带的extend方法可以用来实现对象的复制. a = {k1:1, k2:2, k3:3}; b = {}; $.extend(b,a); 自定义clone

笔记——JS的对象复制

JS的对象复制-- Js的Number类型和String类型都不是地址引用,而是重新创建对象复制值:var a = 1;var b = a;b = 2;alert(a);结果:1 var c = "abc";var d = c;d = "def";alert(c);结果:abc 只有对象类型才是地址引用的:var a = {x:1};var b = a;b.x = 2;alert(a.x);结果:2 所以复制对象不要用"=",而是遍历对象复制对象

通过反射克隆对象,对象复制(克隆)工具类

最近做的项目中,经常会遇到用视图来操作数据库的,但是页面需要的则是某个实体对象,在controller层查出list<view> 还要把将view对象转化成entity对象.需要写一大堆的get和set方法,而且如果实体增删字段的话,还需要把转化代码再修改一下,让人头疼. 当我需要操作一个实体对象完成两件不同的事情,这2个方法中会修改实体对象中的属性,第一个方法调用后,再调用第二个方法时,会受影响.为了保证不受影响,必须copy一份属性值一模一样的实体.这时候就需要一个工具类来完成了. 本着磨

Java反射 - 2(对象复制,父类域)

为什么要复制对象?假设有个类Car,包含name,color2个属性,那么将car1对象复制给car2对象,只需要car2.setName(car1.getName)与car2.setColor(car1.getColor)两部操作即可. 实际项目中有很多类都有超过几十个,甚至上百个字段,这时如果采用以上方式,将会有很大的代码工作量与重复劳动.解决方法是使用反射机制. 首先有2个类如下 1 /** 2 * Created by yesiming on 16-11-19. 3 */ 4 publi

通过反射克隆对象,对象复制(克隆),对象合并工具类 升级版

先上代码,有时间再详说: package com.kaiyuan.common.util; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 通用对象co

关于delete和对象复制

本码农的惯例,开篇废话几句... 前天小生又被虐了... 没办法,作为一个资深code user,我用代码的能力,解决问题的能力自问是不弱的... 但是自身的前端基础说实话还是不过硬,最明显的表现就是,对于JS核心的研究做得比较少. 另外就是概念方面很脆弱(PS:我的习惯是用通俗的比喻来理解技术,那些乏味的概念实在记不住几个) 现实依旧一如既往的残酷,许多平时用的很少的知识点经常被用作体现一个人能力水平的指标. 其实也是合理的,毕竟用的少就不去研究,这是说不过去的. OK,废话完毕,开始正题.