继续整理第五章的内容,关于对象复制的。
对于默认的拷贝赋值操作符,在如下情况下不会表现出按位拷贝(bitwise copy:关于按位拷贝,实际就是不使用拷贝构造函数或者拷贝赋值操作符,这里的不使用是指编译器根本不会产生,而是采用按位拷贝对象数据的方式,若对象中含有指针,此时的指针只是地址级别的浅拷贝,可能会引起内存问题):
a. 当类内带有一个含有拷贝赋值操作符的成员变量时。
b. 当类的基类含有拷贝赋值操作符时。
c. 当类声明了虚函数时,此时不能直接拷贝右端类对象的虚函数指针,因为右边可能是子类对象。
d. 当类继承自虚基类时(不管此虚基类有无拷贝赋值操作符)。
例如:
class Point
{
public:
Point(float x = 0.0, float y = 0.0);
//没有虚函数
protected:
float _x, _y;
};
含有拷贝赋值操作符:
inline Point& Point::operator=(const Point& p)
{
_x = p._x;
_y = p._y;
return *this;
}
此时,有一个Point3d类虚拟继承自Point类:
class Point3d : virtual public Point {
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
//...
protected:
float _z;
};
由于上述b和d原因的存在,此时编译器会为Point3d合成一个拷贝赋值操作符(由于Point3d未定义此操作符),看起来像这样:
inline Point3d& Point3d::operator=(Point3d* const this, const Point3d& p)
{
//invoke base's operator=
this->Point::operator=(p);//or can invoke like this: (*(Point*)this) = p;
//memberwise copy the derived class members
_z = p.z;
return *this;
}
注意,对于拷贝赋值操作符,编译器没有办法压制对于基类的多次拷贝赋值(可以结合上一篇中关于编译器如何压制子类构造函数中多次构造基类的问题),例如,类Vertex也虚拟继承自Point(//class Vertex: virtual public Point { //... };),Vertex3d派生自Point3d和Vertex,Vertex3d的拷贝赋值运算符看起来是这样的,此时由于Point3d和Vertex的operator=中也会调用Point::operator=,所以此时Point::operator=在类Vertex3d中调用了三次:
inline Vertex3d& Vertex3d::operator=(const Vertex3d& v)
{
this->Point::operator=(v);
this->Point3d::operator=(v);//会调用:this->Point::operator=(v);
this->Vertex::operator=(v);//也会调用:this->Point::operator=(v);
}
之所以会出现重复调用是因为获取拷贝赋值操作符的地址是合法的(关于成员函数指针可以参考中,”指向成员函数的指针部分“[读书笔记] 深入探索C++对象模型-第四章-Function语义学(下)):
typedef Point3d& (Point3d::* pmfPoint3d)(const Point3d&);
pmfPoint3d pmf = &Point3d::operator=;
(p.*pmf)(x);
关于此点,C++标准中的说法是:“我们并没有规定那些代表虚基类的子对象是否应该被隐喻定义的(implicitly defined)拷贝赋值操作符指派(assign)内容一次以上。”
由此可见,拷贝赋值操作符在虚拟继承的情况下行为不佳。
因此一条建议就是:不要在任何虚基类中声明数据成员。