实现一个不抛异常的swap函数

template<typename T>
void swap(T& a, T& b)
{
    T temp(a);  // 拷贝构造
    a = b;      // 拷贝赋值运算符
    b = temp;   // 拷贝赋值运算符
}

一个基本的swap函数大概如上所述,但是对某些类型而言。典型比如:pimpl手法(pointer to implementation),这些复制没有必要。

class WidgetImpl
{
    public:
        ...
    private:
        int a, b, c;
        std::vector<double> v;    // 意味着拷贝开销很大
};

class Widget
{
    public:
        Widget(const Widget& rhs);
        Widget& operator=(const Widget& rhs)
        {
            ...
            *pImpl = *(rhs.pImpl);
        }
    private:
        WidgetImpl* pImpl;
}

一旦需要置换两个Widget对象值,我们只需要交换pImpl指针,但缺省的swap算法不知道这一点,它不止拷贝三个Widget,还拷贝三个WidgetImpl对象。

我们希望能够告诉std::swap,当Widgets被置换时真正该做的是置换其内部的pImpl指针,确切的做法就是:将std::swap针对Widget特化。

namespace std {
    template<>
    void swap<Widget>(Widget& a, Widget& b)
    {
        swap(a.pImpl, b.pImpl);
    }
}

通常我们不被允许改变std命名空间内的任何东西,但可以为标准的template制造特化版本。
虽然上面的代码看起来达到我们的目的,但实际上这个函数无法通过编译,因为它企图访问 a 和 b 内的Impl指针,而那是private的。
但我们可以在内部实现一个swappublic成员函数做真正的置换工作,然后将std::swap特化,令它调用成员函数:

class Widget
{
    public:
        ...
        void swap(Widget& other)
        {
            using std::swap;
            swap(pImpl, other.pImpl);
        }
    ...
};

namespace std
{
    template<>
    void swap<Widget>(Widget& a, Widget &b)
    {
        a.swap(b);
    }
}

这种做法不仅能通过编译,还与STL容器有一致性,因为所有的STL容器也都提供有public swap成员函数和std::swap特化版本。

  • using std::swap

  • 不抛异常
    成员版swap最好实现成不要抛异常,因为swap的一个最好应用是帮助class提供异常安全保障。此技术基于一个假设:成员版的swap绝不抛异常。但这一约束只约束于成员版,不可施行于非成员版,因为swap缺省版本是以拷贝构造和拷贝赋值操作符为基础,而一般情况下两者都允许抛异常。

总结
  • 如果swap的缺省实现对你的classclass template提供可接受的效率。那么不需要做额外的操作。
    如果swap的缺省实现版本效率不足,试着做下面的事情:
    • 提供一个public swap成员函数,让它高效的置换你的类型的两个对象值,并且这个swap函数绝不能抛出异常。
    • 在你的classtemplate所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数。
    • 如果你在编写一个class而非class template,为你的class特化std::swap
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容