读书笔记 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 ...
随机推荐
- 6、手把手教你Extjs5(六)继承自定义一个控件
Extjs的开发都可以遵循OOP的原则,其对类的封装也很完善了.自定义一个控件最简单的办法就是继承一个已有的控件.根据上一节的需要,我做了一个Button的子类.首先根据目录结构,在app目录下建立一 ...
- ubuntu升级php版本
如果安装的 PHP 版本过低的话,可以通过下面的指令来升级: sudo add-apt-repository ppa:ondrej/php5 sudo apt-get update sudo ...
- webform中 ajax调用后台方法(非webservice)
方法一:通过创建一个没有内容的窗体 后台: public partial class Ajax_ShoppingCart : System.Web.UI.Page { bookdbDataContex ...
- BZOJ 1101 [POI2007]Zap ——Dirichlet积
[题目分析] Dirichlet积+莫比乌斯函数. 对于莫比乌斯函数直接筛出处理前缀和. 对于后面向下取整的部分,可以分成sqrt(n)+sqrt(m)部分分别计算 学习了一下线性筛法. 积性函数可以 ...
- js传递数组到后台
//post方法不得行,各种问题...改为ajax就阔以了.//默认的话,traditional为false,即jquery会深度序列化参数对象,以适应如PHP和Ruby on Rails框架, // ...
- IOS开发中如何判断程序第一次启动(根据判断结果决定是否显示新手操作引导)
IOS开发中如何判断程序第一次启动 在软件下载安装完成后,第一次启动往往需要显示一个新手操作引导,来告诉用户怎么操作这个app,这就需要在程序一开始运行就判断程序是否第一次启动,如果是,则显示新手操作 ...
- 文件查找和比较命令 来自: http://man.linuxde.net/find
文件查找和比较1.find命令,用来在指定目录下查找文件.任何位于参数之前的字符串都将被视为欲查找的目录名.如果使用该命令时不设置任何参数,则find命令则在当前目录下查找子目录与文件.并且将查到的子 ...
- Javascript 查找元素
DOM定义了多种查找元素的方法,除了我们常用的getElementById(),还有getElementsByTagName()和getElementsByName().使用这几种方法方法我们可以查找 ...
- error: Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier
xcode + iwatch调试错误 在工程的 Targets 下面的 三项(工程名为my):my . my Watchkit app .my Watchkit extention General ...
- swift 定位 根据定位到的经纬度转换城市名
好久没写随笔了 最近这段时间项目有点紧 天天在加班 国庆 一天假都没放 我滴娃娃 好啦 牢骚就不发了 毕竟没有什么毛用 待我那天闲了专门写一篇吐槽的随笔