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

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

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

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

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

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

通常我们不能改变标准库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);
    }
}

这种做法不但可以通过编译,而且与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);
    }
}

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

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

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

不幸的是这也编译不通过,因为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);
    }
}

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

3. 假设对于以下函数:

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

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

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

"一旦编译器看到对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总是基于对内置类型的操作,而内置类型上的操作绝不会抛出异常".

时间: 2024-10-23 08:55:28

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

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

1 // lib中的swap 2 namespace std { 3 template<typename T> 4 void swap (T& a, T& b) 5 { 6 T temp(a); 7 a = b; 8 b = temp; 9 } 10 } 11 12 // 缺点:需要赋值大量的数据,但是有的时候并不要复制如此多的内容 13 class WidgetImpl { 14 public: 15 //... 16 private: 17 int a, b, c; 18

《Effective C 》资源管理:条款25--考虑写出一个不抛出异常的swap函数

条款25考虑写出一个不抛出异常的swap函数 条款25:考虑写出一个不抛出异常的swap函数 swap是STL中的标准函数,用于交换两个对象的数值.后来swap成为异常安全编程(exception-safe programming,条款29)的脊柱,也是实现自我赋值(条款11)的一个常见机制.swap的实现如下: namespace std{ template<typename T> void swap(T& a, T& b) { T temp(a); a=b; b=temp;

读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数

在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& a) { A temp = a; swap(temp); return *this; } 上面的代码看起来有点麻烦,但它是一个好办法. 我们可以在std里面特化我们的swap class A { private: int a; public: void swap(A& x)//防止写成friend,我

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

条款25:考虑写出一个不抛异常的swap函数 swap函数在C++中是一个非常重要的函数,但实现也非常复杂. 看一个缺省的std::swap函数的实现 namespace std { template<typename T> void swap( T& a , T& b) { T temp(a); a = b; b = temp } } ①内置类型的调用 int a = 2; int b =3; std::swap(a, b); cout<<"a:&quo

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

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常 示例: stl里的swap算法 namespace std{ template<typename T> void swap(T &a, T &b){ T temp(a); a = b; b = temp; } } //"pimpl手法"(pointer

EC读书笔记系列之13:条款25 考虑写出一个不抛异常的swap函数

记住: ★当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定其不抛出异常 ★若你提供一个member swap,也该提供一个non-member swap来调用前者.对于classes(而非templates),也请特化std::swap ★调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰” ★为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西 --

More Effective C++ 条款12 了解”抛出一个exception&quot;与“传递一个参数”或“调用一个虚函数”之间的差异

1. 函数return值与try块throw exception.函数接收参数与catch字句捕获异常相当类似(不仅声明形式相像,函数参数与exception传递方式都有三种:by value,by reference , ). 2. 尽管函数调用与异常抛出相当类似,“从抛出端传递一个exception到catch子句”和“从函数调用端传递一个实参到被调函数参数”仍然大有不同: 1)调用一个函数,控制权会最终回到调用端(除非函数失败以致无法返回),但是抛出一个exception,控制权不会再回到

Effective C++ 条款25

考虑写出一个不抛出异常的swap函数 本节讲解如何自定义一个高效的swap函数 对于std名空间中的swap缺省函数如下所示 namespace std{ template<typename T> void swap(T& a, T& b) { T temp(a); a=b; b=temp; } } class WidgetImpl{ public: -- private: int a,b,c; //数据很多,复制意味时间很长 std::vector<double>

《Effective C++》item25:考虑写出一个不抛异常的swap函数

std::swap()是个很有用的函数,它可以用来交换两个变量的值,包括用户自定义的类型,只要类型支持copying操作,尤其是在STL中使用的很多,例如: int main(int argc, _TCHAR* argv[]) { int a[10] = {1,2,3,4,5,6,7,8,9,10}; vector<int> vec1(a, a + 4); vector<int> vec2(a + 5, a + 10); swap(vec1, vec2); for (int i =