分享
 
 
 

翻译:Effective C++, 3rd Edition, Item 12: 拷贝一个对象的所有组成部分

王朝c/c++·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

Item 12: 拷贝一个对象的所有组成部分

在设计良好的面向对象系统中,为了压缩其对象内部的空间,仅留两个函数用于对象的拷贝:一般称为拷贝构造函数(copy constructor)和拷贝赋值运算符(copy assignment operator)。我们将它们统称为拷贝函数(copying functions)。Item 5 讲述了如果需要,编译器会生成拷贝函数,而且阐明了编译器生成的版本正象你所期望的:它们拷贝被拷贝对象的全部数据。

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

考虑一个象征消费者(customers)的类,这里的拷贝函数是手写的,以便将对它们的调用记入日志:

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 中加入了另外的数据成员:

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

class Customer {

public:

... // as before

private:

std::string name;

Date lastTransaction;

};

在这里,已有的拷贝函数只进行了部分拷贝:它们拷贝了 Customer 的 name,但没有拷贝它的 lastTransaction。然而,大部分编译器对此毫不在意,即使是在最高的警告级别(maximal warning level)(参见 Item 53)。这是它们在对你写自己的拷贝函数进行报复。你拒绝了它们写的拷贝函数,所以如果你的代码是不完善的,他们也不告诉你。结论显而易见:如果你为一个类增加了一个数据成员,你务必要做到更新拷贝函数。(你还需要更新类中的全部的构造函数(参见 Item 4 和 45)以及任何非标准形式的 operator=(Item 10 给出了一个例子)。如果你忘记了,编译器未必会提醒你。)

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

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 的拷贝函数看上去好像拷贝了 PriorityCustomer 中的每一样东西,但是再看一下。是的,它确实拷贝了 PriorityCustomer 声明的数据成员,但是每个 PriorityCustomer 还包括一份它从 Customer 继承来的数据成员的副本,而那些数据成员根本没有被拷贝!PriorityCustomer 的拷贝构造函数没有指定传递给它的基类构造函数的参数(也就是说,在它的成员初始化列表中没有提及 Customer),所以,PriorityCustomer 对象的 Customer 部分被 Customer 的构造函数在无参数的情况下初始化——使用缺省构造函数。(假设它有,如果没有,代码将无法编译。)那个构造函数为 name 和 lastTransaction 进行一次缺省的初始化。

对于 PriorityCustomer 的拷贝赋值运算符,情况有些微的不同。它不会试图用任何方法改变它的基类的数据成员,所以它们将保持不变。

无论何时,你打算自己为一个派生类写拷贝函数时,你必须注意同时拷贝基类部分。那些地方的典型特征当然是 private(参见 Item 22),所以你不能直接访问它们。派生类的拷贝函数必须调用和它们对应的基类函数:

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" 的含义现在应该清楚了。当你写一个拷贝函数,需要保证(1)拷贝所有本地数据成员以及(2)调用所有基类中的适当的拷贝函数。

在实际中,两个拷贝函数经常有相似的函数体,而这一点可能吸引你试图通过用一个函数调用另一个来避免代码重复。你希望避免代码重复的想法值得肯定,但是用一个拷贝函数调用另一个来做到这一点是错误的。

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

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

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

Things to Remember

拷贝函数应该保证拷贝一个对象的所有数据成员以及所有的基类部分。不要试图依据一个拷贝函数实现另一个。作为代替,将通用功能放入第三个供双方调用的函数。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有