读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数

我也不知道为什么作者给这个条款起这样的名字,因为这样看上去重点是在“不抛出异常”,但事实上作者只是在全文最后一段说了一下不抛异常的原因,大部分段落是在介绍怎样写一个节省资源的swap函数。

你可以试一下,只要包含了头文件iostream,就可以使用swap函数,比如:

1 #include <iostream>
2
3 int main()
4 {
5     int a = 3;
6     int b = 4;
7     std::swap(a, b);
8 }

结果就是a为4,b为3了,也就是说,在std的命名空间内,已经有现成的swap的函数了,这个swap函数很简单,它看起来像这样:

1 template<class T>
2 void swap(T& a, T& b)
3 {
4     T c = a;
5     a = b;
6     b = c;
7 }

这是最常见形式的两数交换了(特别地,当T是整数的时候,还可以使用异或的操作,这不是本条款讨论的重点,所以略过了,但面试题里面很喜欢问这个)。

假设存在一个类Element,类中的元素比较占空间:

1 class Element
2 {
3 private:
4     int a;
5     int b;
6     vector<double> c;
7 };

Sample类中的私有成员是Element的指针,有原生指针,大多数情况下都需要自定义析构函数、拷贝构造函数和赋值运算符,像下面一样。

1 class Sample
2 {
3 private:
4     Element* p;
5 public:
6     ~Sample();
7     Sample(const Sample&);
8     Sample& operator= (const Sample&);
9 };

在实现operator=的时候,有一个很好的实现方法,参见条款十一。大概像这样:

1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, temp);
7     }
8     return *this;
9 }

当判断不是自我赋值后,是通过调用拷贝构造函数来创建一个临时的对象(这里可能会有异常,比如不能分配空间等等),如果这个对象因异常没有创建成功,那么下面的swap就不执行,这样不会破坏this的原始值,如果这个对象创建成功了,那么swap一下之后,把临时对象的值换成*this的值,达到了赋值的效果。

上面的解释是条款九的内容,如果不记得了,可以回头翻翻看,本条款的重点在这个swap函数上。这里调用的是默认的std里面的swap函数,它会创建一个临时的Sample对象(拷贝构造函数),然后调用两次赋值运算,这就会调回来了,即在swap函数里面调用operator=,而之前又是在operator=中调用swap函数,这可不行,会造成无穷递归,堆栈会溢出。

因此,我们要写一份自己的swap函数,这个函数是将Sample里面的成员进行交换。

问题又来了,Sample里面存放的是指向Element的指针,那是交换指针好呢,还是逐一交换指针所指向的对象里面的内容好呢?Element里面的东西挺多的,所以显然还是直接交换指针比较好(本质是交换了Element对象存放的地址)。

因此,可以定义一个swap的成员函数。像这样:

 1 void swap(Sample& s)
 2 {
 3     std::swap(p, s.p);
 4 }
 5 Sample& operator= (const Sample& s)
 6 {
 7     if(this != &s)
 8     {
 9         Sample temp(s);
10         this->swap(s);
11     }
12     return *this;
13 }

但这样看上去有点别扭,我们习惯的是像swap(a, b)这种形式的swap,如果交给其他程序员使用,他们也希望在类外能够像swap(SampleObj1, SampleObj2)那样使用,而不是SampleObj1.swap(SampleObj2)。为此我们可以在std空间里面定义一个全特化的版本(std namespace是不能随便添加东西的,只允许添加类似于swap这样的全特化版本),像这样:

1 namespace std
2 {
3 template<>
4 void swap<Sample>(Sample &s1, Sample &s2)
5 {
6     s1.swap(s2); // 在这里调用类的成员函数
7 }
8 }

重写operator=,像下面这样:

1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, s); // 顺眼多了,会先去调用特化版本的swap
7     }
8     return *this;
9 }

这样,就可以在使用namespace std的地方用swap()函数交换两个Sample对象了。

下面书上的内容就变难了,因为假设Sample现在是一个模板类,Element也是模板类,即:

1 template <class T>
2 class Element
3 {…};
4
5 template <class T>
6 class Sample
7 {…};

那应该怎么做呢?

在模板下特化std的swap是不合法的(这叫做偏特化,编译器不允许在std里面偏特化),只能将之定义在自定义的空间中,比如:

 1 namespace mysample
 2 {
 3     template <class T>
 4 class Element
 5 {…};
 6
 7 template <class T>
 8 class Sample
 9 {…};
10
11 template <class T>
12 void swap(Sample<T> &s1, Sample<T> &s2)
13 {
14     s1.swap(s2);
15 }
16 }

总结一下,当是普通类时,可以将swap的特化版本放在std的namespace中,swap指定函数时会优先调用这个特化版本;当是模板类时,只能将swap的偏特化版本放在自定义的namespace中。好了,问题来了,这时候用swap(SampleObj1, SampleObj2)时,调用的是std版本的swap,还是自定义namespace的swap?

事实上,编译器还是会优先考虑用户定义的特化版本,只有当这个版本不符合调用类型时,才会去调用std的swap。但注意此时:

1 Sample& operator= (const Sample& s)
2 {
3     if(this != &s)
4     {
5         Sample temp(s);
6         swap(*this, s); // 前面的swap不要加std::
7     }
8     return *this;
9 }

里面的swap不要用std::swap,因为这样做,编译器就会认为你故意不去调用位于samplespace里面的偏特化版本了,而去强制调用std命名空间里的。

为了防止出这个错,书上还是建议当Sample是普通类时,在std命名空间里定义一个全特化版本。

这个条款有些难度,我们总结一下:

1. 在类中提供了一个public swap成员函数,这个函数直接交换指针本身(因为指针本身是int类型的,所以会调用std的普通swap函数),像下面这样:

1 void Sample::swap(Sample &s)
2 {
3     swap(p, s.p); // 也可以写成std::swap(this->p, s.p);
4 }

2. 在与Sample在同一个namespace的空间里提供一个non-member swap,并令他调用成员函数里的swap,像下面这样:

1 template <>
2 void swap<Sample>(Sample& s1, Sample& s2){s1.swap(s2);} // 如果Sample是普通类,则定义swap位于mysample空间中,同时多定义一个位于std空间中(这个多定义不是必须的,只是防御式编程)

或者

1 template <class T>
2 void swap(Sample<T>& s1, Sample<T>& s2){s1.swap(s2);} // 如果Sample是模板类时,只能定义在mysample空间中

 

好了,最后一段终于说到了不抛异常的问题,书上提到的是不要在成员函数的那个swap里抛出异常,因为成员函数的swap往往都是简单私有成员(包括指针)的置换,比如交换两个int值之类,都是交换基本类型的,不需要抛出异常,把抛出异常的任务交给non-member的swap吧。

 

最后总结一下:

1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,这个成员函数不抛出异常,只对内置类型进行操作

2. 如果提供一个member swap,也该提供一个non-member swap来调用前者,对于普通类,也请特化std::swap

3. 调用swap时,区分是调用自身命名空间的swap还是std的swap,不能乱加std::符号

4. 为“用户自定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

 

时间: 2024-09-28 21:42:05

读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数的相关文章

WPF and Silverlight学习笔记(二十五)

WPF and Silverlight学习笔记(二十五):使用CollectionView实现对绑定数据的排序.筛选.分组 在第二十三节,我们使用CollectionView实现了对于绑定数据的导航,除导 航功能外,还可以通过CollectionView对数据进行类似于DataView的排序.筛选 等功能. 一.数据的排序: 使用第二十四节的数据源,查询所有 的产品信息: 1: <Window x:Class="WPF_24.CollectionViewSortData" 2:

PHP读书笔记整理_结构语句详解_php实例

PHP结构语句顺序结构 顺序结构就像一条直线,按着顺序一直往下执行.我们编写的代码默认都是按照顺序结构执行的. 条件结构之if-else- 条件结构就像一个岔路口,可以向左走,也可以向右走.比如上洗手间,我们知道我们的性 别,这时候我们需要根据洗手间提供的条件,左边男洗手间,右边女洗手间,或者正好相反,其中性别就是这个条件结构的条件.再比如,现在的分数都流行使用 A.B.C来分级,假设考试成绩是93分,可以将其设置为等级A,考试成绩是87,可以将其设置为等级B,这里分数区间即为条件结构中的条件.

Windows 8风格应用开发入门 二十五 数据绑定

数据绑定是一种简单方式来显示数据,UI元素与数据对象之间的连接或绑定是允许数据在两者之间 流动的.另外建立了绑定且数据发生变化时,相应的UI元素会自动显示变化. 如何将UI元素与 数据进行绑定 开发入门 二十五 数据绑定-windows开发入门"> 从上面图可以知道,每个绑定必须指定一个源和一个目标. 其中源对象可以是任何CLR对象,包括目标元素自身和其他UI元素.目标可以是 FrameworkElement的任何DependencyProperty(依赖属性). 数据绑定引擎从Bindi

C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(二十五)

C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(二十五)完美捕捉精灵之神器 -- HitTest 怪物们都出现了,如何选中自己心仪的怪是主角目前首要做的事. 为了进行鼠标状态区别,我首先对鼠标变化规则进行约束:当鼠标在屏幕上空旷地图区域移动时,鼠标光标形态表现为默认光标 (0号光标图片),当鼠标经过精灵(悬停于其上方)时则变成发光光标(1号光标图片),如果指向的精灵对象为敌对状态时则鼠标光标变为攻击光标(2号光标图片),当使用魔法快捷键时,鼠标光标变成凝法状

Bootstrap &lt;基础二十五&gt;警告(Alerts)

原文:Bootstrap <基础二十五>警告(Alerts) 警告(Alerts)以及 Bootstrap 所提供的用于警告的 class.警告(Alerts)向用户提供了一种定义消息样式的方式.它们为典型的用户操作提供了上下文信息反馈. 您可以为警告框添加一个可选的关闭按钮.为了创建一个内联的可取消的警告框,请使用 警告(Alerts) jQuery 插件. 您可以通过创建一个 <div>,并向其添加一个 .alert class 和四个上下文 class(即 .alert-su

赶紧接着上一节:Silverlight+WCF 新手实例 象棋 主界面-实时聊天区(二十五) 这节我们实现上节没实现的纠结的进出房间的消息提示 我们清楚的知道,我们每个区都是一个用户控件,我们的在线用户进出时,得到聊天区域显示信息,这就涉及到用户控件之间的消息传递了。 在线用户区说:反正我是

赶紧接着上一节:Silverlight+WCF 新手实例 象棋 主界面-实时聊天区(二十五)  这节我们实现上节没实现的纠结的进出房间的消息提示 我们清楚的知道,我们每个区都是一个用户控件,我们的在线用户进出时,得到聊天区域显示信息,这就涉及到用户控件之间的消息传递了. 在线用户区说:反正我是没权直接去实时聊天区写东西,没办法,找中介委托设置一下了. 那谁是中介呢?委托谁呢?当然是衣食父母Index.xaml了,是它撑着整个家庭的. 好了,知道中介了,那我就签个委托书了: 回到OnlineUse

Visual Basic 二十五周年,微软是否应该开源?

1991年5月20日,比尔盖茨在Windows World 上发布了Visual Basic.微软官方博客上周庆祝了VB二十五周年.自新CEO上任之后,微软的企业文化被认为发生了很大改变,开始积极拥抱开源.因此在二十五周年之际,粉丝再次在官网呼吁微软开源VB,以前的多次呼吁都遭到了微软的拒绝.但最新的呼吁看起来结局也一样,用户在官网发表的帖子被合并到了旧的帖子. 文章转载自 开源中国社区[http://www.oschina.net]

微信小程序把玩(二十五)loading组件

原文:微信小程序把玩(二十五)loading组件 loading通常使用在请求网络数据时的一种方式,通过hidden属性设置显示与否 主要属性: wxml <!----> <button type="primary" bindtap="listenerButton">显示loading</button> <!--默认隐藏--> <loading hidden="{{hiddenLoading}}&quo

在社交媒体交友、建立用户群的二十五个方法

如果你的客户和你的企业建立了自己的社交档案和渠道.现在一个最大的问题就是如何建立朋友.用户和跟随者们的大型社区.我在办公室作了一些研究,并得到了很多的建议,以下是其中的25个.当然,还有更多的建议,但以下的25个建议是一个好的开始. 一.设置个人广告.使用在线媒体(显示旗帜.http://www.aliyun.com/zixun/aggregation/12592.html">Flash广告.微件广告等)为你的社交媒体渠道带来流量.Facebook网页可以作为出色的登陆网页,他们也会允许你