条款25(三):考虑写出一个不抛异常的swap函数

127 篇文章 7 订阅
39 篇文章 3 订阅

条款25:考虑写出一个不抛异常的swap函数

Consider support for a non-throwing swap.
在第二部分中,我们不要添加任何新东西到std内。

但我们还是需要一个办法,以提供高效的template特定版本的swap。解决办法是:

  • 依然是声明一个non-member swap,让它调用member swap但不再将那个non-member swap声明为std::swap的特别版本或重载版本

因此,为了简化起见,假设Widget的所有相关机能被置于命名空间WidgetStuff,于是:

namespace WidgetStuff {
    ...                         //模板化的WidgetImpl等等
    template<typename T>        //和前面一样,内含swap成员函数
    class Widget { ... };
    ...

    template<typename T>        //non-member swap函数
    void swap(Widget<T>& a,     //这里并不属于std命名空间
              Widget<T>& b)
    {
        a.swap(b);
    }
}   

于是,当我们置换两个Widget对象,因而:

-调用swap,C++的名称查找法则(name lookup rules;更具体的说是所谓argument-dependent lookup或Kobeig lookup法则)将会找到WidgetStuff内的Widget专属版本。

这正是我们所希望的。

然而,虽然上面的做法对于class和class template都行得通,但我们还是应该为class特化std::swap
所以,如果我们想让“class 专属版”的swap在尽可能多的语境下被调用,我们就应该同时在该class所在命名空间内写一个non-member版本以及一个std::swap特化版本。

用户角度

上面所说的swap,一直是从我们自身角度去考虑。如果我们从用户的角度来看,对swap进行定义也非常有必要。假设正在写一个function template,其内需要置换两个对象值:

template<typename T>
void doSomething(T& obj1, T& obj2)
{
    ...
    swap(obj1, obj2);
    ...
}

此时,调用了swap,但是调用的是哪一个一般化版本?

  • std既有的一般化版本?
  • 某个可能存在的特化版本?
  • 存在的T专属版本而且可能存在与某个命名空间内(非std内)

我们希望的是调用T专属版本,并在该版本不存在的情况下,再去调用std内的一般化版本

template<typename T>
void doSomething(T& obj1, T& obj2)
{
    usint std::swap;    //令std::swap在此函数内可用
    ...
    swap(obj1, obj2);   //为T型对象调用最佳swap版本
    ...
}

一旦编译器看到了对swap的调用,它们便查找适当的swap并加以调用。C++的名称查找发着会确保将找到global作用域或者T所在的命名空间内的任何T专属的swap。
如果T是Widget并位于命名空间WidgetStuff内,编译器就会使用“实参取决的查找规则”(argument-dependent lookup)找出WidgetStuff内的swap。
如果没有T专属的swap存在,编译器就会使用std内的swap——由using std::swap这条语句,使得这个选择被展现。
当然,如果已经针对T将std::swap进行了特化,这个特化版本也直接会被优先使用。

因此,令适当的swap被调用是比较容易的。但需要小心的是:

  • 不要添加额外的修饰符,这样会影响C++挑选适当的函数:
std::swap(obj1, obj2);      //错误的swap调用方式

上面这个举动,会迫使编译器只认std内的swap,因而不再可能调用一个定义于其他地方的适当T专属版本

到目前为止,这三部分已经讨论了:

  • default swap
  • member swap
  • non-member swap
  • std::swap 特化版本
  • swap的调用

因此,做一个总结:
首先,如果swap的缺省实现对我们的class或class template提供可接受的效率,那么我们并不需要做其他的事情。

其次,如果swap的缺省版本效率不足(基本上就是因为class或者class template使用了某种pimpl手法),则:

  • 1,提供一个public swap成员函数,让它高效地置换对应类型的两个对象值。这个函数绝不能抛出异常!
  • 2,在我们的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
  • 3,如果我们正在编写一个class(而非class template),为我们的class特化std::swap。并令它调用我们的swap成员函数。

最后,如果我们调用swap,请确定包含一个using声明式,以便让std::swap在我们的函数内部可以曝光可见,然后不加任何namespace修饰符,直接去调用swap。

最后,还有一点:

  • 成员版本的swap绝对不可以抛出异常!

原因在于,swap的一个最好的应用就是为了帮助class(和class template)提供强烈的异常安全性(exception-safety)保障。
当然,这一约束只施行于成员版!不可实施于非成员版,因为swap缺省版本是以copy构造函数和copy assignment操作符为基础的,在一般情况下是允许抛出异常的。。
因此,当我们写一个自定义版本的swap时,往往需要提供以下两点:

  • 高效置换对象值的办法
  • 不抛出异常

一般而言,上面这两个特性是连在一起的,因为高效率的swap几乎总是基于对内置类型的操作(例如pimpl首发的底层指针),而内置类型上的操作绝不会抛出异常。

最后:

1,当std::swap对你的效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

2,如果你提供一个member swap,也应该提供一个non-member swap用来调用前者。对于classes(而非template),也请特化std::swap。

3,调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值