《Effective C 》资源管理:条款25

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

只要T支持copying函数(copy构造函数和copy assignment操作符)就能允许swap函数。这个版本的实现非常简单,a复制到temp,b复制到a,最后temp复制到b。

但是对于某些类型而言,这些复制可能无一必要。例如,class中含有指针,指针指向真正的数据。这种设计常见的表现形式是所谓的“pimpl手法“(pointer to implementation,条款31)。如果以这种手法设计Widget class

class WidgetImpl{public:……private:int a,b,c;//数据很多,复制意味时间很长std::vector<double> b;……};

下面是pimpl实现

class Widget{public:Widget(const Widget& rhs);Widget& operator=(const Widget& rhs{……//复制Widget时,复制WidgetImpl对象*pImpl=*(ths.pImpl);……}……private:WidgetImpl* pImpl;//指针,含有Widget的数据};

如果置换两个Widget对象值,只需要置换其pImpl指针,但STL中的swap算法不知道这一点,它不只是复制三个Widgets,还复制WidgetImpl对象,非常低效。

我们希望告诉std::swap,当Widget被置换时,只需要置换其内部的pImpl指针即可,下面是基本构想,但这个形式无法编译(不能访问private)。

namespace std{template<>//这是std::swap针对T是Widget的特换版本,void swap<Widget>(Widget& a, Widget& b) //目前还无法编译{//只需要置换指针swap(a.pImpl, b.pImpl); }}

其中template<>表示std::swap的一个全特化(total template specialization),函数名之后的<Widget>表示这一特化版本系针对T是Widget而设计的。我们被允许改变std命名空间的任何代码,但是可以为标准的template编写特化版本,使它专属于我们自己的class。

上面函数试图访问private数据,因此无法编译。我们可以将swap函数声明为friend,但这个和以往有点不同。可以令Widget的swap函数为public,然后将std::swap特化

calss Widget{public:……void swap(Widget& other){using std::swap;//这个声明有必要swap(pImpl, other.pImpl);}……};namespace std{template<> //修订后的swap版本void swap<Widget>(Widget& a, Widget& b){a.swap(b); //调用其成员函数}}

这个做法还跟STL容器保持一致,,因为STL容器也提供public swap和特化的std::swap(用来条用前者)。 刚刚假设Widget和WidgetImpl都是class,而不是class template,如果是template时:

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

可以在Widget内或WidgetImpl内放个swap成员函数,像上面一样。但是在特化std:swap 时会遇到麻烦

namespace std{template<typename T>void swap<Widget<T> >(Widget<T>& a,//不合法,错误Widget<T>& b){a.swap(b);}}

看起来合理却不合法。上面是企图偏特化(partially specialize)一个function template(std::swap),但C++只允许对class template偏特化,在function templates身上偏特化行不通,这段代码不该通过编译。 当偏特化一个function template时,通常简单地为它添加一个重载版本

namespace std{template<typename T>//std::swap一个重载版本void swap(Widget<T>& a,//swap后面没有<……>Widget<T>& b)//这个也不合法{a.swap(b);}}

一般而言,重载function template没有任何问题,但std是个特殊的命名空间,其管理规则也比较特殊。客户可以全特化std内的templates,但是不可以添加新的classes或functions到std里面。std的内容有c++标准委员会决定,标准委员会禁止我们膨胀那些已经 声明好的东西。 正确的做法是声明一个non-member swap 让他来调用member swap,但不再将那个non-member swap声明为std::swap。把Widget相关机能都置于命名空间WidgetStuff

namespace WidgetStuff{ ……Widget{……}; …… template<typename T> void swap(Widget<T>& a,//non-member,不属于std命名空间Widget<T>& b) {a.swap(b); }}

上面的做法对于class和class template都适用,但是如果你想让你的“class专属版”swap在尽可能多的语境下被调用,你需要同时在该class所在命名空间内写一个non-member版本以及一个std::swap版本。 现在所做的都与swap相关,换位思考一下,从客户角度来看,假设你正在写一个function template,其内需要置换两个对象的值

template<typename T>void doSomething(T& obj1, T& obj2){……swap(obj1, obj2);……}快乐要懂得分享,才能加倍的快乐

《Effective C 》资源管理:条款25

相关文章:

你感兴趣的文章:

标签云: