[翻译] Effective C++, 3rd Edition, Item 12: 拷贝一个 object(对象)的所有 parts(构件)

Item 12: 拷贝一个 object(对象)的所有 parts(构件)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

在设计良好的 object-oriented systems(面向对象系统)中,封装了 object(对象)的内部构件,只有两个拷贝 objects(对象)的函数:被恰当地称为 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)。我们将它们统称为 copying functions(拷贝函数)。Item 5 讲述了如果需要,编译器会生成 copying functions(拷贝函数),而且阐明了编译器生成的版本正像你所期望的:它们拷贝被拷贝 object(对象)的全部数据。

当你声明了你自己的 copying functions(拷贝函数),你就是在告诉编译器你不喜欢缺省实现中的某些东西。编译器对此好像怒发冲冠,而且它们会用一种古怪的方式报复:当你的实现几乎可以确定是错误的时,它们偏偏不告诉你。

考虑一个代表 customers(顾客)的 class(类),这里的 copying functions(拷贝函数)是手写的,以便将对它们的调用记入日志:

void logCall(const std::string& funcName);       // make a log entry

class Customer {
public:
  ...
  Customer(const Customer& rhs);
  Customer& operator=(const Customer& rhs);
  ...

private:
  std::string name;
};
Customer::Customer(const Customer& rhs)
: name(rhs.name)                                 // copy rhs's data
{
  logCall("Customer copy constructor");
}

Customer& Customer::operator=(const Customer& rhs)
{
  logCall("Customer copy assignment operator");

  name = rhs.name;                               // copy rhs's data

  return *this;                                  // see Item 10
}

这里的每一件事看起来都不错,实际上也确实不错——直到 Customer 中加入了其它 data member(数据成员):

class Date { ... };       // for dates in time

class Customer {
public:
  ...                     // as before

private:
  std::string name;
  Date lastTransaction;
};

在这里,已有的 copying functions(拷贝函数)只进行了 partial copy(部分拷贝):它们拷贝了 customers(顾客)的 name,但没有拷贝他的 lastTransaction。然而,大多数编译器即使是在 maximal warning level(最高警告级别)(参见 Item 53),对此也是一声不吭。这是它们在对你自己写 copying functions(拷贝函数)进行报复。你拒绝了它们写的 copying functions(拷贝函数),所以即使你的代码是不完善的,它们也不告诉你。结论显而易见:如果你为一个 class(类)增加了一个 data member(数据成员),你必须确保你也更新了 copying functions(拷贝函数)。(你还必须更新 class(类)中的全部 constructors(构造函数)(参见 Items 445)以及任何非标准形式的 operator=Item 10 给出了一个例子)。如果你忘记了,编译器未必会提醒你。)

这个问题最为迷惑人的情形之一是它会通过 inheritance(继承)发生。考虑:

class PriorityCustomer: public Customer {                  // a derived class
public:
   ...
   PriorityCustomer(const PriorityCustomer& rhs);
   PriorityCustomer& operator=(const PriorityCustomer& rhs);
   ...

private:
   int priority;
};
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: priority(rhs.priority)
{
  logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
  logCall("PriorityCustomer copy assignment operator");

  priority = rhs.priority;

  return *this;

}

PriorityCustomer 的 copying functions(拷贝函数)看上去好像是拷贝了 PriorityCustomer 中的每一样东西,但是再看一下。是的,它拷贝了 PriorityCustomer 声明的 data member(数据成员),但是每个 PriorityCustomer 还包括一份它从 Customer 继承来的 data members(数据成员)的拷贝,而那些 data members(数据成员)根本没有被拷贝!PriorityCustomer 的 copy constructor(拷贝构造函数)没有指定传递给它的 base class constructor(基类构造函数)的参数(也就是说,在它的 member initialization list(成员初始化列表)中没有提及 Customer),所以,PriorityCustomer object(对象)的 Customer 部分被 Customer 的不取得 arguments(实参)的 constructor(构造函数)—— default constructor(缺省构造函数)初始化。(假设它有,如果没有,代码将无法编译。)那个 constructor(构造函数)为 namelastTransaction 进行一次 default initialization(缺省初始化)。

对于 PriorityCustomer 的 copy assignment operator(拷贝赋值运算符),情况只是稍微有些不同。它不会试图用任何方法改变它的 base class data members(基类数据成员),所以它们将保持不变。

无论何时,你打算自己为一个 derived class(派生类)写 copying functions(拷贝函数)时,你必须注意同时拷贝 base class parts(基类构件)。当然,那些构件一般是 private(私有)的(参见 Item 22),所以你不能直接访问它们。derived class(派生类)的 copying functions(拷贝函数)必须调用与它们相对应的 base class functions(基类函数):

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:    Customer(rhs),                   // invoke base class copy ctor
  priority(rhs.priority)
{
  logCall("PriorityCustomer copy constructor");
}

PriorityCustomer&
PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
  logCall("PriorityCustomer copy assignment operator");

  Customer::operator=(rhs);           // assign base class parts
  priority = rhs.priority;

  return *this;
}

本 Item 标题中的 "copy all parts" 的含义现在应该清楚了。当你写一个 copying functions(拷贝函数),需要保证(1)拷贝所有 local data members(局部数据成员)以及(2)调用所有 base classes(基类)中的适当的 copying function(拷贝函数)。

实际上,两个 copying functions(拷贝函数)经常有相似的函数体,而这一点可能吸引你试图通过用一个函数调用另一个来避免 code duplication(代码重复)。你希望避免 code duplication(代码重复)的愿望值得肯定,但是用一个 copying functions(拷贝函数)调用另一个来实现它是错误的。

用 copy assignment operator(拷贝赋值运算符)调用 copy constructor(拷贝构造函数)是没有意义的,因为你这样做就是试图去构造一个已经存在的 object(对象)。这太荒谬了,甚至没有一种语法来支持它。有一种语法看起来好像能让你这样做,但实际上你做不到,还有一种语法采用迂回的方法这样做,但它们在某种条件下会对破坏你的 object(对象)。所以我不打算给你看任何那样的语法。无条件地接受这个观点:不要用 copy assignment operator(拷贝赋值运算符)调用 copy constructor(拷贝构造函数)。

尝试一下另一种相反的方法——用 copy constructor(拷贝构造函数)调用 copy assignment operator(拷贝赋值运算符)——这同样是荒谬的。一个 constructor(构造函数)初始化新的 objects(对象),而一个 assignment operator(赋值运算符)只能应用于已经初始化过的 objects(对象)。借助构造过程给一个 object(对象)assignment(赋值)将意味着对一个 not-yet-initialized object(尚未初始化的对象)做一些事,而这些事只有用于 initialized object(已初始化对象)才有意义。简直是胡搞!禁止尝试。

作为一种代替,如果你发现你的 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)有相似的代码,通过创建一个供两者调用的第三方 member function(成员函数)来消除重复。这样一个函数一般是 private(私有)的,而且经常叫做 init。这一策略是在 copy constructor(拷贝构造函数)和 copy assignment operator(拷贝赋值运算符)中消除 code duplication(代码重复)的安全的,被证实过的方法。

Things to Remember

  • copying functions(拷贝函数)应该保证拷贝一个 object(对象)的所有 data members(数据成员)以及所有的 base class parts(基类构件)。
  • 不要试图依据一个 copying functions(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值