代码改变世界

Effective C++ 学习笔记(13)

2011-08-03 22:57  Daniel Zheng  阅读(197)  评论(0编辑  收藏  举报

在operator=中对所有数据成员赋值


template<class T>
class NamedPtr
{
public:
NamedPtr(
const string & initName, T * initPtr);
NamedPtr
& operator= (const NamedPtr & rhs);

private:
string name;
T
* ptr;
};

template
<class T>
NamedPtr
<T> & NamedPtr<T>::operator=(const NamedPtr<T> & rhs)
{
if(this == &rhs)
return *this;

name
= rhs.name;
*ptr = *rhs.ptr;

return *this;
}

  当在类里面增加新的数据成员时,要记得更新赋值运算符。

  特别注意当涉及的继承时的情况,派生类的赋值运算符也必须处理它的基类的赋值。

  

class Base
{
public:
Base(
int initialValue = 0):x(initialValue) {}
private:
int x;
};

class Derived:public Base
{
public:
Derived(
int initialValue):Base(initialValue),y(initialValue) {}

Derived
& operator=(const Derived & rhs);

private:
int y;
}

 逻辑上说,Derived的赋值运算符应该如下:

Derived & Derived::operator=(const Derived & rhs)
{
if(this == &rhs)
return *this;

y
= rhs.y;

return *this;
}

  然而这是错误的,因为Derived对象的Base部分的数据成员x在赋值运算符中未受影响,考虑一下代码:

void assignmentTester()
{
Derived d1(
0); //d1.x = 0, d1.y = 0
Derived d2(1); //d2.x = 1, d2.y = 1

d1
= d2; //d1.x = 0, d1.y = 1
}

  d1的Base部分没有受operator=的影响。

  解决这个问题最显然的办法是在Derived::operator=中对x赋值,但这不合法,因为x是Base的私有成员,所以必须在Derived的赋值运算符里显示的对Derived的Base部分赋值。

  

Derived & Derived::operator=(const Derived & rhs)
{
if(this == &rhs)
return *this;

Base::
operator=(rhs);
y
= rhs.y;

return *this;
}

  如果基类赋值运算符是编译器生成的,有些编译器会拒绝这种对于基类赋值运算符的调用。为了适应这种编译器,必须这样实现:

Derived & Derived::operator=(const Derived & rhs)
{
if(this == &rhs)
return *this;

static_cast
<Base &>(*this) = rhs; //对*this的Base部分调用operator=

y
= rhs.y;

return *this;
}

  这段怪异的代码将*this 强制转换为Base 的引用,然后对其转换结果赋值。这里只是对Derived 对象的Base 部分赋值。还要注意的重要一点是,转换的是Base 对象的引用,而不是Base 对象本身。如果将*this 强制转换为Base 对象,就要导致调用Base 的拷贝构造函数,创建出来的新对象就成为了赋值的目标,而*this 保持不变。这不是所想要的结果。

  不管采用哪一种方法,在给 Derived 对象的Base 部分赋值后,紧接着是Derived 本身的赋值,即对Derived 的所有数据成员赋值。另一个经常发生的和继承有关的类似问题是在实现派生类的拷贝构造函数时。看看下面这个构造函数,其代码和上面刚讨论的类似:

class Base 
{
public:
Base(
int initialValue = 0): x(initialValue) {}
Base(
const Base& rhs): x(rhs.x) {}

private:
int x;
};
class Derived: public Base
{
public:
Derived(
int initialValue) : Base(initialValue), y(initialValue) {}
Derived(
const Derived& rhs) // 错误的拷贝
: y(rhs.y) {} // 构造函数

private:
int y;
};

  类 Derived 展现了一个在所有C++环境下都会产生的bug:当Derived 的拷贝创建时,没有拷贝其基类部分。当然,这个Derived 对象的Base 部分还是创建了,但它是用Base 的缺省构造函数创建的,成员x 被初始化为0(缺省构造函数的缺省参数值),而没有顾及被拷贝的对象的x 值是多少!

  为避免这个问题,Derived 的拷贝构造函数必须保证调用的是Base 的拷贝构造函数而不是Base 的缺省构造函数。这很容易做,只要在Derived 的拷贝构造函数的成员初始化列表里对Base 指定一个初始化值:

class Derived: public Base 
{
public:
Derived(
const Derived& rhs): Base(rhs), y(rhs.y) {}
...
};

  现在,当用一个已有的同类型的对象来拷贝创建一个 Derived 对象时,它的Base 部分也将被拷贝了。