[翻译] 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 4 和 45)以及任何非标准形式的 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(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。
作者: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 4 和 45)以及任何非标准形式的 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(拷贝函数)实现另一个。作为代替,将通用功能放入一个供双方调用的第三方函数。
相关文章推荐
- [翻译] Effective C++, 3rd Edition, Item 21: 当你必须返回一个 object(对象)时不要试图返回一个 reference(引用)(下)
- [翻译] Effective C++, 3rd Edition, Item 21: 当你必须返回一个 object(对象)时不要试图返回一个 reference(引用)(上)
- 【Effective C++ 3rd 心得、归纳、实践】 Item 12: 拷贝一个对象的所有组成部分
- effective c++ Item 12: 拷贝一个对象的所有组成部分
- [翻译] Effective C++, 3rd Edition, Item 28: 避免返回 object 内部构件的 "handles"(“句柄”)
- [翻译] Effective C++, 3rd Edition, Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- [翻译] Effective C++, 3rd Edition, Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- [翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(下)
- [翻译] Effective C++, 3rd Edition, Item 14: 谨慎考虑 resource-managing classes(资源管理类)中的拷贝行为
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
- [翻译] Effective C++, 3rd Edition, Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- [翻译] Effective C++, 3rd Edition, Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- [翻译] Effective C++, 3rd Edition, Item 4: 确保 objects(对象)在使用前被初始化
- [翻译] Effective C++, 3rd Edition, Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- [翻译] Effective C++, 3rd Edition, Item 25: 考虑支持一个 non-throwing swap(不抛异常的 swap)(上)
- [翻译] Effective C++, 3rd Edition, Item 13: 使用 objects(对象)管理资源
- [翻译] Effective C++, 3rd Edition, Item 17: 在 standalone statements(独立语句)中将 new 出来的 objects(对象)存入 smart pointers(智能指针)
- [翻译] Effective C++, 3rd Edition, Item 24: 当希望将 type conversions(类型转换)应用于所有 parameters(参数)时,请声明为 non-member functions(非成员函数)
- [翻译] Effective C++, 3rd Edition, Item 49: 了解 new-handler 的行为(下)