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

1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下:

namespace std{
    template<typename T>
    swap(T& lhs,T& rhs){
        T temp(lhs);
        lhs=rhs;
        rhs=temp;
    }
}
View Code

只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某些类,这种默认的转换方式代价太大,比如:

class Demo{
public:
    Demo(const Demo&);
    Demo& operator=(const Demo& rhs){
        ...
        *ptr=*rhs.ptr;
        ...
    }
    ...
private:
    vector<int>* ptr;
}
View Code

如果按照标准库swap的默认行为所付出的的代价很大,而实际上只要置换两个指针的地址即可.

2. 解决1的一种方法是对标准库std命名空间内的swap函数模板进行特化,如下:

namespace std{
    template<>
    void swap<Demo>(Demo& lhs,Demo& rhs){
        swap(lhs.ptr,rhs.ptr);
    }
}
View Code

通常我们不能改变标准库std命名空间内的东西,但是C++允许为标准templates制造特化版本,因此以上代码理论上来说是可行的,但由于Demo的ptr成员被设为private,所以以上代码通不过编译.我们可以为Demo声明一个名为swap的public成员函数执行相关操作,然后令标准库特化swap调用该函数,如下:

class Demo{
public:
    ...
    void swap(Demo& rhs){
        using std::swap;
        swap(ptr,rhs.ptr);
    }
    ...
}
namespace std{
    void swap(Demo& lhs,Demo& rhs){
        lhs.swap(rhs);
    }
}
View Code

这种做法不但可以通过编译,而且与STL保持一致,因为"所有STL容器也都提供有public成员函数和std::swap特化版本"

如果Demo是class template,那么情况就要发生变化,例如:

template<typename T>
class Demo{ ... }

如果要采用以上方法,在Demo类模板内定义一个swap成员函数是可行的,但是要特化std::swap时势必要这样:

namespace std{
    template<typename T>
    void swap<Demo<T> >(Demo<T>& lhs,Demo<T>& rhs){
        lhs.swap(rhs);
    }
}
View Code

结果通不过编译,因为C++标准目前只允许对class templates偏特化,而不允许对function templates偏特化(注:偏特化指的是将一个模板特化为另一个模板,例如部分类型参数特化以及将类型参数特化为容器类型参数(如上))

另一个方法是为std::swap添加一个重载版本,如下:

namespace std{
    template<typename T>
    void swap(Demo<T>& lhs,Demo<T>& rhs){//注意没有swap之后<>,所以这是重载不是特化
        lhs.swap(rhs);
    }
}
View Code

不幸的是这也编译不通过,因为C++虽然允许对标准库templates进行特化,"但不可以添加新的templates(或classes或functions或其他任何东西)"到std里。

还有一种方法,与以上相同,声明一个non-member swap让它调用member swap,但不再将那个non-member声明为std::swap的特化或重载版本,而是置于另一个命名空间DemoStuff内,如下:

namespace DemoStuff{
    ...
    template<typename T>
    class Demo{...}
    ...
    template<typename T>
    void swap(Demo<T>& lhs,Demo<T>& rhs){
        lhs.swap(rhs);
    }
}
View Code

当然,以上也可以声明在全局命名空间内,但是这样可能会造成作用域的杂乱无章(用《Effective C++中文版》来说,要保持"得体与适度").

 3. 假设对于以下函数:

template<typename T>
void doSomething{T& lhs,T& rhs){
    ...
    swap(lhs,rhs);
    ...
}
View Code

如果想要调用T的专属swap版本,并在该笨笨不存在的情况下,调用std内的一般化版本,可以按以下定义:

template<typename T>
void doSomething{T& lhs,T& rhs){
    using std::swap;
    ...
    swap(lhs,rhs);
    ...
}
View Code

"一旦编译器看到对swap的调用,它们便寻找适当的swap并调用之.C++的名称查找法则(name lookup roles)确保将找到global作用域或T所在之命名空间内的任何T专属的swap."如果T是Demo并未与DemoStuff命名空间内,编译器会使用"实参取决之查找规则"(argument-dependent lookup)找出DemoStuff之内的swap."如果没有T专属之swap存在,编译器就调用std内的swap".(using std::swap并没有指定出现的swap是std内的版本,而是使std内的swap在函数内可见,如果按"std::swap(obj1,obj2)"的方式调用,调用的必是std内的版本)

3. 成员版(指的是以上的高效率版)swap绝不可抛出异常!正如以上所言,swap的一个重要应用是"帮助classes(或class templates)提供强烈的异常安全性(exception-safety)保障",但这一技术只适用于成员版(高效率版),因为默认版本的swap要调用拷贝构造函数和拷贝赋值操作符,而这两种函数都允许抛出异常.因此对于自定义的高效版本往往提供的不只是高效置换值的方法,而且是不抛出异常."一般而言这两种swap特性是连在一起的,因为高效率的swap总是基于对内置类型的操作,而内置类型上的操作绝不会抛出异常".

posted @ 2015-08-30 21:30  Reasno  阅读(482)  评论(0编辑  收藏  举报