读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍
在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:拷贝构造函数和拷贝赋值运算符。我们把这两个函数统一叫做拷贝函数。从Item5中,我们得知,如果需要的话编译器会为你生成这两个拷贝函数,并且编译器生成的版本能够精确的做到你想做的:它们拷贝了对象的所有数据。
2.自己实现构造函数有可能出现问题
当你声明自己的拷贝函数的时候,你就会向编译器表示,你对编译器生成版本的拷贝函数有些地方不是很喜欢。你的这种做法会让编译器以一种奇怪的方式进行报复:如果你自己实现的拷贝函数出现了问题,编译器不会告诉你。
2.1 问题出现场景一
考虑一个表示消费者的类,类中的拷贝函数已经被手动实现了,所以调用它们会被记入日志:
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.但大多数编译器会对这种实现默不发声,甚至一个警告级别的信息也不会发出来(看Item 53)。编译器对你自己写的拷贝函数进行了报复。你拒绝使用它们提供的拷贝函数,于是它们不会告诉你代码是否是完整的。结论很明显:如果你向类中添加一个数据成员,你需要确保同时对拷贝函数进行更新。(你同时需要更新类中所有的构造函数(Item4和Item45)和任何非标准形式的operator=(Item 10给出了一个例子)),如果你忘记了,编译器不会提醒你。
2.2 更加阴险的方式-场景二
使这个问题出现的最阴险的方式是通过继承。看下面的例子:
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的每个对象同时包含了从Customer继承过来的数据成员,这部分数据没有被拷贝!PriorityCustomer的拷贝构造函数没有指定传到基类构造函数的参数(也就是说没有在成员初始化列表中列出Customer),所以PriorityCustomer对象的Customer部分会被Customer的无参构造函数进行初始化。(肯定会有一个,不然编译会出错。)这个构造函数会为name 和 lastTransaction执行一个默认初始化。
对于PriorityCustomer的拷贝构造运算符来说情形有些不同。它并没有以任何方式去尝试修改基类的数据成员,因此它们可以保持不变。
3.如何才能避免上面的问题
在任何时候你自己去为一个派生类实现拷贝构造函数的时候,你必须注意需要同时拷贝基类部分。这些部分当然有可能是Private的(见Item22),所以你不能直接访问它们。但是,派生类的拷贝函数必须调用对应的基类构造函数:
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;
}
在这个条款的标题中,“拷贝所有部分”的意思现在应该明了了。当你实现一个拷贝函数的时候,确保(1)拷贝所有本地的数据成员。(2)同时调用所有基类的合适的拷贝函数。
4.如何才能解决构造函数中的代码重复问题
在实际应用中,这两个拷贝函数通常有着类似的函数体,这会让你尝试通过一个函数调用另一个函数以达到避免代码重复的目的。你的这种想避免代码重复的愿望是值得赞赏的,但为了达到避免代码重复,用一个拷贝函数调用另外一个是错误的方法。
4.1 用赋值运算符调用拷贝构造函数-错误!
用拷贝赋值运算符来调用拷贝构造函数是没有意义的,因为你正在尝试构建一个已经存在的对象。这是荒谬的,也没有这样的语法。看上去有一些能够到达你要求的语法,但实际上不是。有一些语法确实能够做到,但在一些情况下会破坏你的对象。所以我不会向你展示这些语法的任何部分。你不想通过拷贝赋值运算符去调用拷贝构造函数,接受这个想法就可以了。
4.2 用拷贝构造函数调用赋值运算符-错误!
相反,使用拷贝函数调用拷贝赋值运算符也同样是没有意义的。一个拷贝构造函数是初始化新的对象的,但是一个赋值运算符只能够应用在已经被初始化的对象上面。在一个对象上通过构造函数来执行赋值就意味着,你正在对一个未初始化的对象做某些事情,但这件事情对初始化的对象才有意义。没有意义,不要去尝试!
4.3 正确的做法-将相同代码提炼成第三个函数
想反,如果你发现你的拷贝构造函数和拷贝赋值运算符有着看上去类似的函数体,通过创建可以同时被两个构造函数调用的第三个成员函数来消除代码重复。这样的函数应该被声明为Private并且通常叫做Init.这个策略是安全的,可以达到消除重复的目的。
读书笔记 effective c++ Item 12 拷贝对象的所有部分的更多相关文章
- 读书笔记 effective c++ Item 13 用对象来管理资源
1.不要手动释放从函数返回的堆资源 假设你正在处理一个模拟Investment的程序库,不同的Investmetn类型从Investment基类继承而来, class Investment { ... ...
- 读书笔记 effective c++ Item 4 确保对象被使用前进行初始化
C++在对象的初始化上是变化无常的,例如看下面的例子: int x; 在一些上下文中,x保证会被初始化成0,在其他一些情况下却不能够保证.看下面的例子: class Point { int x,y; ...
- 读书笔记 effective c++ Item 14 对资源管理类的拷贝行为要谨慎
1. 自己实现一个资源管理类 Item 13中介绍了 “资源获取之时也是初始化之时(RAII)”的概念,这个概念被当作资源管理类的“脊柱“,也描述了auto_ptr和tr1::shared_ptr是如 ...
- 读书笔记 effective c++ Item 25 实现一个不抛出异常的swap
1. swap如此重要 Swap是一个非常有趣的函数,最初作为STL的一部分来介绍,它已然变成了异常安全编程的中流砥柱(Item 29),也是在拷贝中应对自我赋值的一种普通机制(Item 11).Sw ...
- 读书笔记 effective c++ Item 17 使用单独语句将new出来的对象放入智能指针
1. 可能会出现资源泄漏的一种用法 假设我们有一个获取进程优先权的函数,还有一个在动态分类的Widget对象上根据进程优先权进行一些操作的函数: int priority(); void proces ...
- 读书笔记 effective c++ Item 28 不要返回指向对象内部数据(internals)的句柄(handles)
假设你正在操作一个Rectangle类.每个矩形可以通过左上角的点和右下角的点来表示.为了保证一个Rectangle对象尽可能小,你可能决定不把定义矩形范围的点存储在Rectangle类中,而是把它放 ...
- 读书笔记 effective c++ Item 5 了解c++默认生成并调用的函数
1 编译器会默认生成哪些函数 什么时候空类不再是一个空类?答案是用c++处理的空类.如果你自己不声明,编译器会为你声明它们自己版本的拷贝构造函数,拷贝赋值运算符和析构函数,如果你一个构造函数都没有声 ...
- 读书笔记 effective c++ Item 6 如果你不想使用编译器自动生成的函数,你需要明确拒绝
问题描述-阻止对象的拷贝 现实生活中的房产中介卖房子,一个服务于这个中介的软件系统很自然的会有一个表示要被销售的房屋的类: class HomeForSale { ... }; 每个房产中介会立刻指出 ...
- 读书笔记 effective c++ Item 11 在operator=中处理自我赋值
1.自我赋值是如何发生的 当一个对象委派给自己的时候,自我赋值就会发生: class Widget { ... }; Widget w; ... w = w; // assignment to sel ...
随机推荐
- uses crt;
1.uses CRT,表示引用CRT.pas单元.CRT.pas单元是Pascal最重要的单元之一,主要用于字符界面的操作,里面内置了清屏.光标定位.删除行.调整字符亮度.前景色.背景色等功能函数.2 ...
- S3C2440时钟系统详解
在讲述系统时钟之前,因为这些设备都是挂靠在系统时钟上的,所以必须先说系统时钟,S3C2440的时钟系统如下 外部时钟源分两种,晶振或者外部频率,由om3-2选择,时钟电路根据两种选择也有两种 我们来分 ...
- LRU Cache 题解
题意 Design and implement a data structure for Least Recently Used (LRU) cache. It should support the ...
- UVa 757 - Gone Fishing
题目大意:John有h的时间可以去钓鱼,有n湖可钓鱼,以5分钟为时间单位,每个湖初始每个单位时间可钓f条鱼,每下一个时间单位数量减少d条.同时,John只能从1号湖往后走进行钓鱼,湖之间的距离需要t个 ...
- iOS 之 微信开发流程
第1阶段 注册开放平台帐号 注册成为微信开放平台开发者 立即注册 认证开发者资质 开发者资质认证通过后才可申请微信支付,申请审核服务费:300元/次 立即认证 创建APP并提交审核 提交你的APP基本 ...
- 笔记整理——linux
linux文件目录介绍 (2015/4/30 19:20:28) /proc 目录中的主要文件的说明 文件或目录名称 描 述 apm 高级电源管理信息 cmdline 这个文 ...
- 【单调栈】hdu1506 Largest Rectangle in a Histogram
单调栈的介绍及一些基本性质 http://blog.csdn.net/liujian20150808/article/details/50752861 依次把矩形塞进单调栈,保持其单增,矩形中的元素是 ...
- Java网络编程总结
网络编程: 端口: 物理端口: 逻辑端口:用于标识进程的逻辑地址,不同进程的标识:有效端口:0~65535,其中0~1024系统使用或保留端口. java 中ip对象:InetAddress. imp ...
- 贴一份用delphi修改注册表改网卡MAC地址的代码
//提示:此代码需要use Registry, Common; function WriteMAC(model:integer):integer; var reg:TRegistry; begin r ...
- 用JS的数组缓存一些东西
var cache_index = new Array(); //首页的ajax缓存 //ajax 推荐的游戏和软件 function change_tuijian(sid,div_class){ i ...