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

版权声明:仅供参考与学习交流 https://blog.csdn.net/lym940928/article/details/81989718

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

Consider support for a non-throwing swap.
在上一部分里,我们明白swap算法无法知道pImpl指针,而我们希望告诉std::swap,当Widgets被置换时,真正应该做的就是置换内部的pImpl指针。
而实现这一想法的做法是

  • 将std::swap针对Widget特化。

下面的代码是思路的实现,但是无法通过编译:

namsespace std {        //这是std::swap针对“T是Widget”的特化版本,并不能通过编译
    tempate<>
    void swap<Widget>(Widget& a, Widget& b)
    {
        swap(a.pImpl, b.pImpl);    //置换Widget时,只需要置换它们的pImpl指针即可
    }
}

对于这个无法编译函数:

  • tempate<>:表示它是std::swap的一个全特化(total template specialization)版本;
  • 函数名之后的:表示这一特化版本会针对“T是Widget”而设计的;
    换句话说,一般性的swap template施加于Widget身上便会启用这个版本。

通常而言,我们不能改变std命名空间内的任何东西,但是可以为标准templates(如swap)制造特化版本,使得它专属于我们自己定义的class(例如Widget)。上面的代码也正是这样去实现的。

而之所以上面的代码无法通过编译,是因为:

  • 它企图访问a和b内的pImpl指针,但这个指针是private

成员函数swap

因此,一个解决办法则是:

  • 令Widget声明一个名为swap的public成员函数,来去做真正的置换工作,然后将std::swap特化,令它调用该成员函数:
class Widget {          //与前面相同,唯一的差别就是增加swap函数
public:
    ...
    void swap(Widget& other)
    {
        using std::swap;   //这个声明是非常必要的
        swap(pImpl, other.pImpl);   //若要置换Widget,就置换其pImpl指针
    }
    ...
};

namespace std {     //修订后的std::swap特化版本
    tempate<>
    void swap<Widget>(Widget& a, Widget& b)
    {
        a.swap(b);    //如果要置换Widgets,调用其swap成员函数
    }
}

在上面的代码中,不仅能够通过编译,而且还与STL容器有一致性

  • 所有STL容器也都提供有public swap成员函数std::swap特化版本(用以调用前者)

特偏化(partially specialize)

然而,假设Widget和WidgetImpl**都是class template而并非class**,也许可以尝试将WidgetImpl内的数据类型加以参数化:

template<typename T>
class WidgetImpl { ... };

temolate<typename T>
class Widget { ... };

在Widget内放一个swap成员函数就像前面一样简单,但是在特化std::swap时却会遇到问题!

namespace std {
    template<typename T>
    void swap< Widget<T> > ( Widget<T>& a,    //错误!
                             Widget<T>& b)
    { a.swap(b); }
}

虽然这样看起来是合理的,然而却并不合法
当我们企图特偏化(partially specialize)一个function template(std::swap)而C++只允许对class template特偏化,在function template身上特偏化是不可以的。

当我们打算偏特化一个function template时,一般的做法是简单地为它添加一个重载模板:

namespace std {
    template<typename T>    //std::swap的一个重载版本
    void swap(Widget<T>& a, //需要注意的是,swap后面没有" <...> "
              Widget<T>& b) //但是这样也是不合法的!
    { a.swap(b); }
}

一般而言,我们是可以重载function template的,但是std是一个特殊的命名空间,因此管理规则也比较特殊:

  • 使用者可以全特化std内的template,但是不可以添加新的template(或者class、function以及其他东西)

因此,所谓的“禁止”,其实添加新东西到std里是可以编译的,但是这样的行为是没有明确定义的。

  • 如果我们希望程序有可预期的行为,就不要添加任何新东西到std之中。

猜你喜欢

转载自blog.csdn.net/lym940928/article/details/81989718