您的位置:首页 > 移动开发 > Objective-C

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

2005-07-16 02:43 573 查看
[b]Item 12: 拷贝一个 object(对象)的所有 parts(构件)[/b]

作者: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(构造函数)为 name 和 lastTransaction 进行一次 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(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐