当我们设计一个class,并以一个class object赋值给另一个class object时,我们就用到了拷贝复制操作,我们可以选择默认的浅拷贝,也可以自己提供一个explicit copy assignment operator,或者显示的拒绝把一个class object赋值给另一个class object(将copy assignment operator声明为private)。
class Point
{
public:
Point(float x = 0.0,float y = 0.0,float z = 0.0);
//...(没有virtual function)
protected:
float _x,_y;
};
对于上述代码,如果要支持一个简单的拷贝操作,那么默认的行为不但足够而且有效率,只有在默认行为所导致语义不安全或不正确的情况下,才需要设计一个copy assignment operator。
由于此class已经有bitwise copy语意,所以implicit copy assignment operator和copy constructor一样被视为毫无用处,也根本不会合成出来。
C++ Standard上说copy assignment operators并不表示bitwise copy semantics是nontrivial,只有nontrivial 才会被合成出来,以下情况不会表现bitwise copy语意:
①当class内含一个member object,而其class有一个copy assignment operator时。
②当一个class 的base class有一个copy assignment operator 时。
③当一个class声明了任何virtual function(我们一定不要拷贝右端class object的vptr地址,因为它可能是一个derived class object)。
④当class 继承自一个virtual base class(不论此时base class有没有copy operator)时。
对于Point class,这样的赋值操作:
Point a,b;
...
a = b;
由于是bitwise copy完成,把Point b拷贝到Point a,期间没有copy assignment operator被调用;我们还可以提供一个copy constructor,把NRV优化打开,copy constructor出现不应该让我们也一定提供一个copy assignment operator。
现在导入一个copy assignment operator,来说明该operator在继承下的行为:
inline Point&
Point::operator = (const Point& rhs)
{
_x = rhs._x;
_y = rhs._y;
return *this;
}
现在派生一个Point3d 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++ pseudo
//被合成的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有一个不够理想、不够严谨的情况,就是它缺乏一个member assignment list(不能像构造函数那样member initialization list),因为不能写:
//C++ pseudo,以下性质不支持
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被调用:
//class Vertex: virtual public Point
inline Vertex&
Vertex::operator = (const Vertex& v)
{
this->Point::operator(v);
_next = v._next;
return *this;
}
//从Point3d和Vertex中派生出Vertex3d
inline Vertex3d&
Vertex3d::operator = (const Vertex3d& v)
{
this->Point::operator(v);
this->Point3d::operator(v);
this->Vertex::operator(v);
//...
}
事实上,copy assignment operator在虚拟继承情况下行为不佳,需要小心的设计和说明。许多编译器甚至并不尝试取得正确语意,它们在每一个中间的copy assignment operator中调用每一个base class instance,于是造成virtual base class copy assignment operator的多个实例被调用。
有一种方法可保证most_derived class会引发virtual base class subobject的copy行为,就是在derived class的copy assignment oper函数实例的最后,显示调用那个operator:
inline Vertex3d&
Vertex3d::operator = (const Vertex3d& v)
{
this->Point3d::operator(v);
this->Vertex::operator(v);
//must place this last if your
//compiler does not suppress
//intermediate class invocations
this->Point::operator(v);
/...
}
这并不能省略subobjects的多重拷贝,但却可以保证语意正确。另一个方法就是把virtual subobject拷贝到一个分离的函数中,并根据call path条件化的调用它。
作者的建议是不要允许virtual baes class的拷贝操作,甚至不要再任何virtual base class中声明数据。