首页 > 代码库 > [Effective C++ --025]考虑写出一个不抛异常的swap函数
[Effective C++ --025]考虑写出一个不抛异常的swap函数
引言
在我的上一篇博客中,讲述了swap函数。
原本swap只是STL的一部分,而后成为异常安全性编程的脊柱,以及用来处理自我赋值可能性。
一、swap函数
标准库的swap函数如下:
1 namespace std {2 template<typename T>3 void swap(T &a, T& b)4 {5 T temp(a);6 a = b;7 b = temp;8 }9 }
只要类型T支持拷贝(通过拷贝构造函数和拷贝赋值操作符完成),那么这个版本的swap函数就会帮你置换类型为T的对象。
二、Pimpl情境下的swap函数
假设我们有如下的类。
1 class WidgetImpl { 2 public: 3 ... 4 private: 5 int a,b,c; // 可能有许多数据 6 std::vector<double> v; // 意味复制时间很长 7 .... 8 } 9 10 class Widget {11 public:12 Widget(const Widget& rhs);13 Widget& operator = (const Widget& rhs)14 {15 .....16 *pImpl = *(rhs.pImpl);17 } 18 private:19 WidgetImpl* pImpl;20 };
一旦要置换两个Widget对象,我们唯一需要做的就是置换其pImpl指针,但是一种的swap函数不知道这一点,它会复制三个Widget和三个WidgetImpl,效率非常低下!
所以我们希望的效果是:只交换pImpl指针!
因此我们可以设计出这个思路:
1 namespace std {2 template<> // 表示它是std::swap的一个特化版本3 void swap<Widget>(Widget &a, Widget& b)4 {5 swap(a.pImpl, b.pImpl);6 }7 }
上述代码当然通不过编译,因为pImpl是私有成员。或许你说我们可以用friend函数来解决这一问题,可是这不大符合预期!
那我们再包装一下:
1 class Widget { 2 public: 3 ... 4 void swap(Widget& other) 5 { 6 using std::swap; 7 swap(pImpl, other.pImpl); // 若置换Widgets就置换其pImpl指针 8 } 9 ....10 }11 12 namespace std {13 template<> // 表示它是std::swap的一个特化版本14 void swap<Widget>(Widget& a, Widget& b)15 {16 a.swap(b);17 }18 }
这样不仅通过编译,还与STL容器具有一致性,因为所有的STL容器也都提供有public swap成员函数和std::swap特化版本。
但需要注意的是:C++只允许对class templates偏特化,在function template上特化是行不通的。如下面代码是编译不过的。
1 namespace std {2 template<typename T> 3 void swap<Widget<T>>(Widget<T>& a, 4 Widget<T>& b)5 {6 swap(a.pImpl, b.pImpl);7 }8 }
三、function template偏特化
那么,如果我们想在function template上特化,应该怎么做呢?答案是添加一个重载版本!
如下:
1 namespace std {2 template<typename T> 3 void swap(Widget<T>& a, // 注意这里没有“<Widget<T>>”4 Widget<T>& b)5 {6 swap(a.pImpl, b.pImpl);7 }8 }
同时,为了其他人调用swap时能取得我们提供的较高版本的template特定版本,我们可以声明一个non-member函数!
1 namespace Widget { 2 template<typename T> 3 class Widget{....} 4 5 template<typename T> 6 void swap(Widget<T>& a, // 注意这里没有“<Widget<T>>” 7 Widget<T>& b) 8 { 9 swap(a.pImpl, b.pImpl);10 }11 }
◆总结
1.当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
2.如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于classes,也请特化std::swap。
3.调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。
4.为“用户定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西
[Effective C++ --025]考虑写出一个不抛异常的swap函数