当我们设计一个 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 中声明数据.