C++ 之 重载赋值操作符
Widget 中,有一个 Bitmap 型指针 pb
class Bitmap; class Widget {
private:
Bitmap *pb; // ptr to a heap-allocated object
};
1 重载 “op=”
在 Widget 类中重载 "=" 时,需考虑以下方面
1.1 链式赋值
整数 15 首先赋值给 z,得到新值的 z 再赋值给 y,接着得到新值的 y 最后再赋值给 x,如下所示:
int x, y, z; x = y = z = ; // chain of assignments
相当于
x = (y = (z = ));
为了实现链式赋值,函数的返回值须是一个实例自身的引用,也即 *this; 同理,重载其它的复合赋值运算符 (如 +=, -=, *=, /=),也必须在函数结束前返回 *this
Widget& Widget::operator=(const Widget& rhs)
{
delete pb; // stop using current bitmap pb = new Bitmap(*rhs.pb); // start using a copy of rhs's bitmap return *this;
}
1.2 自赋值
其次要考虑的是,关于自赋值的情况,虽然显式的自赋值并不常见,但潜在的隐式自赋值仍需注意
Widget w;
...
w = w; // explict assignment to self a[i] = a[j]; // potential assignment to self *px = *py; // potential assignment to self
解决方法是,在函数内加一个 if 语句,判断当前实例 (*this) 和传入的参数 rhs 是不是同一个实例,也即判断是不是自赋值的情况
如果是自赋值,则不作任何处理,直接返回 *this;如果不是自赋值,首先释放实例自身已有内存,然后再分配新的内存,如下所示:
Widget& Widget::operator=(cosnt Widget& rhs)
{
if (this == &rhs) return *this; // identity test: if a self-assignment, do nothing delete pb;
pb = new Bitmap(*rhs.pb); return *this;
}
1.3 异常安全
上例中,假如在分配内存时,因内存不足或 Bitmap 的拷贝构造函数异常,导致 "new Bitmap" 产生异常 (exception),则 pb 指向的是一个已经被删除的 Bitmap
考虑异常安全,一个方法是先用 new 分配新内容,再用 delete 释放如下代码的内容,如下所示:当 "new Bitmap" 抛出一个异常时,pb 指针并不会改变
Widget& Widget::operator=(cosnt Widget& rhs)
{
if (this == &rhs) return *this; // identity test
Bitmap *pOrig = pb; // remember original pb pb = new Bitmap(*rhs.pb); // 注意:"." 的优先级高于 "*"
delete pOrig; // delete the original pb return *this;
}
如果不考虑效率的问题,那么即使没有对自赋值进行判断的 if 语句,其后面的语句也足以应付自赋值的问题
2 拷贝-交换
上例中,因为效率的问题,保留了 if 语句,但实际上,因为自赋值出现的概率很低,所以上述代码看似“高效”,其实并不然
最常用的兼顾自赋值和异常安全 (exception safety) 的方法是 “拷贝-交换” (copy-and-swap),如下所示:
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs); // make a copy of rhs's data swap(temp); // swap *this's data with the copy's return *this;
}
2.1 std::swap
std::swap 属于标准算法,其实现如下:
namespace std
{
template<typename T> // typical implementation of std::swap
void swap(T& a, T& b) // swaps a's and b's values
{
T temp(a);
a = b;
b = temp;
}
}
以上有三个拷贝:首先拷贝 a 给 temp,然后拷贝 b 给 a,最后拷贝 temp 给 b
2.2 Widget::swap
对于 Widget 类,实现两个 Widget 对象的值交换,只需互换 Bitmap *pb 即可,这称为 pimpl (pointer to implementation)
首先,定义一个 swap 公有成员函数,如下:
void Widget::swap(Widget& other)
{
using std::swap;
swap(pb, other.pb); // to swap Widgets, swap their pb pointers
}
然后,模板特例化 std::swap 函数,调用上面的 swap 函数,实现指针互换
namespace std
{
template<> // revised specialization of std::swap
void swap<Widget>(Widget& a, Widget& b)
{
a.swap(b); // to swap Widgets, call their swap member function
}
}
3 智能指针
综上所述,重载赋值操作符,需要考虑链式赋值、自赋值和异常安全,颇为繁琐
一个简化方法是,在 Widget 类中声明一个智能指针
class Widget {
...
private:
std::unique_ptr<Bitmap> pBitmap; // smart pointer
};
此时,重载 "op=",则只需考虑链式赋值
Widget& Widget::operator=(const Widget& rhs) // copy operator=
{
*pBitmap = *rhs.pBitmap; // "." 的优先级高于 "*"
return *this;
}
理论上应该可行,尚未在实际项目中验证 (留待后续测试...)
小结:
1) 重载类赋值操作符,首先考虑链式赋值 -- 函数返回 *this,其次考虑自赋值和异常安全 -- “拷贝-交换”
2) 考虑写一个不抛异常的 swap 函数 (consider support for a non-throwing swap)
3) 被重载的类赋值操作符 "op=" 必须定义为成员函数,其它的复合赋值操作符 (如 "+=", "-=" 等) 应该被定义为成员函数
4) 类中使用智能指针,可大大简化重载赋值操作符 “op=” 的实现
参考资料:
<Effective C++_3rd> item 10, 11, 25
<剑指 offer> 2.2.1
<Effective Modern C++> item 22
C++ 之 重载赋值操作符的更多相关文章
- C++中复制构造函数与重载赋值操作符总结
前言 这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义: 2.复制构造函数和重载赋值操作符的调用时机: 3.复制构造函数和重载赋值操作符 ...
- C++中复制构造函数与重载赋值操作符
我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类: class CTe ...
- 5.5 C++重载赋值操作符
参考:http://www.weixueyuan.net/view/6383.html 总结: 重载赋值操作符同重载类的是拷贝构造函数的原因是一样,将一个对象拷贝给另一个对象,同时当类中存在指针类型的 ...
- Effective C++(10) 重载赋值操作符时,返回该对象的引用(retrun *this)
问题聚焦: 这个准则比较简短,但是往往就是这种细节的地方,可以提高你的代码质量. 细节决定成败,让我们一起学习这条重载赋值操作符时需要遵守的准则吧. 还是以一个例子开始: Demo // 连锁赋值 x ...
- C++重载赋值操作符
1.C++中重载赋值操作函数应该返回什么? 类重载赋值操作符一般都是作为成员函数而存在的,那函数应该返回什么类型呢?参考内置类型的赋值操作,例如 int x,y,z; x=y=z=15; 赋值行为相当 ...
- C++ 数组操作符重载、函数对象分析、赋值操作符
string类型访问单个字符 #include <iostream> #include <string> #include <sstream> using name ...
- C++中的赋值操作符重载和拷贝构造函数
1,关于赋值的疑问: 1,什么时候需要重载赋值操作符? 2,编译器是否提供默认的赋值操作符? 2,关于赋值的疑问: 1,编译器为每个类默认重载了赋值操作符: 1,意味着同类型的类对象可以相互赋值: 2 ...
- 【c++】c++中重载输出操作符,为什么要返回引用
针对:ostream & operator <<(ostream & os, const ClassType &object) 说明几点: 1.第一个形参为对ost ...
- [C++]复制构造函数、赋值操作符与隐式类类型转换
问题:现有类A定义如下: class A{public: A(int a) //构造函数 { ...
随机推荐
- Typecast 免费了!献给设计师们的礼物
TypeCast 让你可以从 Fonts.com.TypeKit.FontDeck 和 Google 这些字体供应和商选择字体,而且能非常方便的比较这些字体使用效果.如果你想获得用户对这些字体效果的反 ...
- Linux下Oracle重启问题
一.切换成oracle用户 $ su - oracle 注意:不要写成“su oracle ”不然是没办法使用下面的命令的. 如下所示:将会报:command not found的错误 二.使用命令 ...
- 极富创意的3D文件夹切换效果
今天分享的是一个极富创意的文件夹切换效果.这个案例使用CSS 3动画实现了一个3D的平行六面体旋转效果.点击顶部的3个按钮可以旋转并切换.另外,每个六面体本身是一个文件夹,点击后可以展开查看里面的详情 ...
- 用这个插件jquery.raty.js————用于星星评价(这个案例用于后台传数据给我,前台显示星星个数)
1.引入jquery.js文件 2.引入jquery.raty.js 3.页面中放在这个:<span class="readOnly" data-path="ima ...
- 从零开始,做一个NodeJS博客(二):实现首页-加载文章列表和详情
标签: NodeJS 0 这个伪系列的第二篇,不过和之前的几篇是同一天写的.三分钟热度貌似还没过. 1 静态资源代理 上一篇,我们是通过判断请求的路径来直接返回结果的.简单粗暴,缺点明显:如果url后 ...
- SharePoint 部署解决方案Feature ID冲突
中文报错: 部署步骤“添加解决方案”中出现错误: 已在此服务器场中安装 ID 为 735efe4e-8b50-4310-b588-c6ae2ba0759f 的功能.请使用强制属性显式地重新安装此功能. ...
- SharePoint 2013 操作文档库ECB菜单
在SharePoint的使用中,我们经常需要定制SharePoint的一系列菜单,这里就包括ECB菜单,下面,我们简单了解一下ECB菜单如何定制,以及原理. 1.正常情况文档库的ECB菜单如下图: 2 ...
- html前端总结
1.设置图片的最大宽高的css<style> .img-max { max-height:50px; width:expression(document.body.clientHeight ...
- KVO的底层实现
1.KVO是基于Runtime机制实现的: 2.当某个类的对象的某个属性第一次被观察时,系统会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法,派生类在被 ...
- 通过settings.db自定义Android系统默认设置
Android的系统设置数据存放在/data/data/com.android.providers.settings/databases/settings.db 中 数据库结构如下: 数据库中的默认数 ...