EC笔记:第二部分:12、复制对象时勿忘其每一个成分
EC笔记:第二部分:12、复制对象时勿忘其每一个成分
1.场景
某些时候,我们不想使用编译器提供的默认拷贝函数(包括拷贝构造函数和赋值运算符),考虑以下类定义:
代码1:
class Point{
private:
double x;
double y;
public:
Point()=default;
Point(const Point &other){
x=other.x;
y=other.y;
}
const Point&
operator=(const Point &other){
x=other.x;
y=other.y;
return
*this;
}
};
Point类重写了拷贝构造函数和赋值运算符,这并没有什么问题,但是,假设现在要新增一个成员:
代码2:
class Point{
private:
double x;
double y;
double r;
//新增的成员
public:
Point()=default;
Point(const Point &other){
x=other.x;
y=other.y;
}
const Point&
operator=(const Point &other){
x=other.x;
y=other.y;
return
*this;
}
};
而且我们的程序员很不巧地忘了更新拷贝构造函数和赋值运算符,那么在复制的时候,新增的1成员r就保持原值,新得到的对象与原对象不相等,与要赋值的对象也不相等。
考虑另外一种场景:
代码3:
class Point{
private:
double x;
double y;
};
class MyPoint:public Point{
private:
double r;
public:
Point()=default;
MyPoint(const MyPoint& other){
r=other.r;
}
const MyPoint&
operator=(const MyPoint& other){
r=other.r;
return
*this;
}
};
当MyPoint继承Point类的时候,重写了拷贝构造函数和赋值运算符,但是忘了赋值基类中的成员,也会导致和前面一样的后果。
2.解决办法
为了解决上面的问题,首先,确保拷贝构造函数和赋值运算符确实将所有的成员都进行了拷贝(除非你是真的想保留一些成员)。例如,代码2中增加了成员r,那么拷贝构造函数和赋值运算符就应该对应更新:
代码4:
class Point{
private:
double x;
double y;
double r;
//新增的成员
public:
Point()=default;
Point(const Point &other){
x=other.x;
y=other.y;
r=other.r;//新增
}
const Point&
operator=(const Point &other){
x=other.x;
y=other.y;
r=other.r;//新增
return
*this;
}
};
其次,子类中重写了赋值运算符或者拷贝构造函数,一定要将父类中隐藏的成员一起复制,通常是调用父类的赋值运算符或者拷贝构造函数。代码3中可以修改为以下代码:
代码5:
class Point{
public:
Point()=default;
private:
double x;
double y;
};
class MyPoint:public Point{
private:
double r;
public:
MyPoint()=default;
MyPoint(const MyPoint& other):Point(other){
r=other.r;
}
const MyPoint&
operator=(const MyPoint& other){
Point::operator=(other);
r=other.r;
return
*this;
}
};
有些童鞋可能注意到,大多数拷贝构造函数与赋值运算符的操作类似,那么,是否可以在拷贝构造函数中调用赋值运算符呢?类似下面的代码:
代码6:
class Point{
public:
Point()=default;
private:
double x;
double y;
};
class MyPoint:public Point{
private:
double r;
public:
MyPoint()=default;
MyPoint(const MyPoint& other){
operator=(other);
}
const MyPoint&
operator=(const MyPoint& other){
Point::operator=(other);
r=other.r;
return
*this;
}
};
对此,《Effective C++》一书给出的解释是:
这段解释比较不是那么易懂,总体说来,就是在执行operator=的时候,拷贝构造函数其实已经执行结束,赋值运算符只作用于已经构造好的对象上,所以此时再使用并无意义(本质上相当于调用了默认构造函数+赋值运算符)。
反过来的话(在赋值运算符中调用拷贝构造函数),由于根本没有类似的语法,所以不做讨论。事实上,构造函数主要目的是"构造",但是调用赋值运算符的时候,对象肯定已经构造完毕,所以,不会有这样的语法支持。
对于拷贝构造函数与赋值运算符有类似代码的场景,《Effective C++》给出的建议是,将公共代码提到一个单独的函数中,各自调用,以上代码可修改为:
代码7:
class Point{
public:
Point()=default;
private:
double x;
double y;
};
class MyPoint:public Point{
private:
double r;
void
init(const MyPoint& other){
r=other.r;
}
public:
MyPoint()=default;
MyPoint(const MyPoint& other):Point(other){
init(other);
}
const MyPoint&
operator=(const MyPoint& other){
Point::operator=(other);
init(other);
return
*this;
}
};
但是对于以上的修改方法,如果是对于这个类,我仍觉得有瑕疵。虽然拷贝构造函数中直接调用了父类的拷贝构造函数,但是在调用init函数的时候,成员r其实已经被构造,整个对象此时已经构造完毕,再对r进行复制,依然会产生额外的开销(当然,这个开销比拷贝构造函数直接调用赋值运算符小)。
所以我建议的修改方式为直接调用每个成员的拷贝构造函数,代码如下:
代码8:
class Point{
public:
Point()=default;
private:
double x;
double y;
};
class MyPoint:public Point{
private:
double r;
public:
MyPoint()=default;
MyPoint(const MyPoint& other):Point(other),r(other.r){
}
const MyPoint&
operator=(const MyPoint& other){
Point::operator=(other);
r=other.r;
return
*this;
}
};
此时Point和r都调用了各自的拷贝构造函数完成构造,比代码7的效率要好一些。
当然,这个修改只是针对这个类,《Effective C++》中指的是拷贝构造函数与赋值运算符中有相同代码部分,此处的修改已经不属于"相同代码"了,因为r=other.r已经被优化为了r(other.r),相当于拷贝构造函数与赋值运算符中已经没有相同代码了。
针对代码7和代码8的分析,可以知道,只有当r没有提供拷贝构造函数的时候,代码7才不能优化为代码8,通常有两种情况:
- r是一个类对象,而且此对象中有些成员未提供拷贝构造函数
- r未提供拷贝构造函数
其实总的就是一句话:r无法通过拷贝构造函数构造,这时才需要用自定义代码完成r的构造。所以为了避免效率上的损失,尽量每个成员都提供拷贝构造函数(除非确实不希望成员被复制,当然,此时对象也不应该有拷贝构造函数)。
3.建议
- 确保每个成员都被复制
- 将不同copy函数中相同部分提取出来,重新封装一个函数,各自调用,两个copy函数不应该存在调用关系。
EC笔记:第二部分:12、复制对象时勿忘其每一个成分的更多相关文章
- EC读书笔记系列之7:条款12 复制对象时勿忘其每一个成分
记住: ★copying函数应确保复制“对象内的所有成员变量”及“所有base class成分” ★不要尝试以某个copying函数实现另一个copying函数.应该将共同机能放进第三个函数中,并由两 ...
- Effective C++_笔记_条款12_复制对象时勿忘其每一个成分
(整理自Effctive C++,转载请注明.整理者:华科小涛@http://www.cnblogs.com/hust-ghtao/) 编译器会在必要时候为我们的classes创建copying函数, ...
- Effective C++ -----条款12: 复制对象时勿忘其每一个成分
Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”. 不要尝试以某个copying函数实现另一个copying函数.应该将共同机能放进第三个函数中,并由两个cop ...
- Effective C++ 条款12:复制对象时勿忘其每一个成分
void logCall(const std::string& funcName); class Customer { public: ... Customer (const Customer ...
- 条款12:复制对象时勿忘其每一个成分(Copy all parts of an object)
NOTE: 1.Copying 函数应该确保复制“对象内的所有成员变量”及“所有base class成分”. 2.不要尝试以某个copying函数实现另一个copying函数.应该将共同机能放进第三个 ...
- [Effective C++ --012]复制对象时勿忘其每一个成分
引言: 在深拷贝和浅拷贝的理解中,我们知道了“拷贝构造函数”一词,并且也了解了它的构成. A(const A& r); // 形式有多种,在这里只列出一个 因此,在值传递的应用场景里,我们可以 ...
- C++复制对象时勿忘每一部分
现看这样一个程序: void logCall(const string& funcname) //标记记录 { cout <<funcname <<endl; } cl ...
- Effective C++(12) 复制对象时要复制每一个成员
问题聚焦: 负责拷贝的两个操作:拷贝构造函数和重载赋值操作符. 一句话总结,确保被拷贝对象的所有成员变量都做一份拷贝. Demo void logCall(const std::string&am ...
- Effective C++ 条款11,12 在operator= 中处理“自我赋值” || 复制对象时不要忘记每一个成分
1.潜在的自我赋值 a[i] = a[j]; *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象, ...
随机推荐
- 安装 Linux 时碰到的硬盘分区的陷阱及应对
硬盘分区的陷阱及应对 之所以想到写这篇,是因为本人在折腾 Linux 系统的过程中,有多次掉入硬盘分区的陷阱的经历.最近几天,再一次掉入坑中,折腾了两天才从坑中爬出来.经过多方查询资料,终于弄明白了硬 ...
- fir.im Weekly - 关于 Log Guru 开源、Xcode 探索和 Android7.0 适配
本期 fir.im Weekly 整理了最近的一些技术分享,包括关于 Log Guru 开源.Xcode 探索. Android7.0 适配等等 iOS/Android 相关的工具.源码分享和技术文章 ...
- iOS-Xcode使用技巧
一.快捷键的使用 经常用到的快捷键如下: 新建 shift + cmd + n 新建项目 cmd + n 新建文件 视图 option + cmd + 回车 打开助理编 ...
- Android仿微信二维码扫描
转载:http://blog.csdn.net/xiaanming/article/details/10163203 了解二维码这个东西还是从微信中,当时微信推出二维码扫描功能,自己感觉挺新颖的,从一 ...
- .NET足球赛事资料数据库平台SmartLottery开源发布——全球足球联赛应有尽有
本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新 开源C#彩票数据资料库系列文章总目录:[目录]C#搭建足球赛事资料库与预测平台与彩票数据分析目录 前2个月,我的系列文 ...
- javascript之一切皆为对象2
其实呢,“函数function”和“对象object”之间还有这么一句话:对象是通过函数来创建的,而函数却又是一种对象. 这个函数是一种对象,上节中“Javascript之一切皆为对象1”也清楚的阐述 ...
- Rust初步(五):Rust与C#性能比较
我学习Rust的目的并不是说期望用它来取代掉现有的开发平台或语言.相反,我认为当前绝大部分研发团队,都不可能只用一个平台或者一个语言. 当组织增长,他们越来越依赖大量的编程语言.不同的编程语言有不同的 ...
- 如何使用Xmanager及VNC登录远程桌面
如何调用远程桌面,比较常见的有两种方式:Xmanager及VNC 正好今天鼓捣了一下,特整理如下: Xmanager Xmanager的调用也有两种方式: 一.直接在Xshell中调用 这时需设置会话 ...
- EF7 Code First Only-所引发的一些“臆想”
At TechEd North America we were excited to announce our plans for EF7, and even demo some very early ...
- 浅析Openflow
这应该算是我在博客园的第一篇技术性的文章. OH,不,这不是一篇技术性的文章,只是一篇很好玩的浅显分析接触到的技术的文章.只是个人的理解,并没有任何的代码和执行的操作.初次带来的,是从08年开始火起来 ...