自学内容网 自学内容网

条款25:考虑写出一个不抛异常的swap函数(Consider support for a non-throwing swap)

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

C++11之前,std::swap是通过标准的交换算法完成的(C++11之后用的是std::move,并明确声明为noexcept)。它的典型实现正是你所期望的:

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;
    }
}

它涉及到将三个对象复制:对于某些类型,这些复制是不必要的。特别是主要由一个指针组成,指向另一种包含实际数据的类型。这种设计方法的常见表现形式是“pimpl(pointer to implementation)方法”(见条款31)。

class WidgetImpl { // 负责Widget数据的类,细节不重要
public: 
    ...
private:
    int a, b, c;// 可能会有很多数据
    std::vector<double> v; // 复制的话,时间会很长!
    ...
};
class Widget { // 使用pimpl方法的类
public:
    Widget(const Widget& rhs);
    Widget& operator=(const Widget& rhs) // 复制Widget时, 复制
    { // 它的WidgetImpl 对象. 
        ... // 
        *pImpl = *(rhs.pImpl); // operator= 的常规实现
        ... // 
    }
 //要交换两个Widget对象的值,真正需要做的就是交换它们的pImpl指针,但默认的交换算法无法知道这一点。
    ...
private:
    WidgetImpl* pImpl; // 指针,指向的对象包含Widget数据
}; 

为Widget特化std::swap。这是它的基本思想,尽管它不能以这种形式编译:

namespace std {
    template<> // 这是一个特化版本
    void swap<Widget>(Widget& a, Widget& b) // 当T是Widget时的std::swap;这无法编译
    {
        //它试图访问a和b中的pImpl指针,它们是私有的,所以无法通过编译!
        swap(a.pImpl, b.pImpl); // 要交换Widget,只需交换它们的pImpl指针
    } 
}

要让Widget声明一个名为swap的公共成员函数来进行实际的交换,然后使用特化的std::swap来调用该成员函数:

class Widget { // 只是增加了swap成员函数
public: 
    ...
    void swap(Widget& other)
    {
        using std::swap; 
        swap(pImpl, other.pImpl); // 交换Widget,只需交换它们的pImpl指针
    } 
    ...
};
namespace std {
    template<> // 修订后的特化版std::swap
    void swap<Widget>(Widget& a,  
        Widget& b)
    {
        a.swap(b); // 交换Widget, 代用它们的swap成员函数
    } 
}

然而,假设Widget和WidgetImpl是类模板而不是类,用于参数化WidgetImpl中的数据类型:

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

namespace std {
    template<typename T>
    //C++允许类模板的偏特化,但不允许函数模板的偏特化。这段代码不应该编译通过(尽管有些编译器会错误地接受它)。
    void swap<Widget<T>>(Widget<T>& a, Widget<T>& b)// 错误! 
    {
        a.swap(b);
    }
}

当你想“部分特化”一个模板函数时,通常的方法是简单地添加一个重载。

namespace std {
//一般来说,重载函数模板是可以的,但std是一个特殊的命名空间。不可以添加新的模板(或类或函数或其他)。这样的行为是未定义的,虽然大多数情况下可以正常编译
    template<typename T> // std::swap的一个重载版本
    void swap(Widget<T>& a, Widget<T>& b) 
    {
        a.swap(b);
    } // 这也不合法
}

如果所有与Widget相关的功能都在命名空间WidgetStuff中

namespace WidgetStuff {
    ... // 模板化的WidgetImpl, 等等.
    template<typename T> // 和以前一样, 包含swap成员函数
    class Widget { ... }; 
    ...
    template<typename T> // 非成员函数版swap,不是std命名空间的一部分
    void swap(Widget<T>& a, Widget<T>& b)
    {
        a.swap(b);
    }

}
template<typename T>
void doSomething(T& obj1, T& obj2)
{
    using std::swap; // 使得 std::swap 在函数内有效
    ...
    swap(obj1, obj2); // 调用更高效的T(Widget)的特化版本
    ...
}

总结

  1. 当std::swap对某类型不够高效时,请提供一个交换成员函数。确保它不会抛出异常。
  2. 如果提供成员函数swap,也要提供调用成员函数的非成员函数swap。对于类(而不是模板),也要特化std::swap。
  3. 在调用swap函数时,先对std::swap使用using声明,然后再调用swap函数,而不需要限定命名空间。
  4. 在std命名空间里可以为用户定义类型进行模板函数的全特化,但绝对不要试图向std中添加全新的东西。

原文地址:https://blog.csdn.net/weixin_43739503/article/details/144661937

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!