首页 > 代码库 > 条款25:考虑写出一个不抛异常的swap函数
条款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:"<<a<<" b:"<<b<<endl;②非内置类型的调用
自我定义的类类型需要实现拷贝构造函数和拷贝赋值函数才能执行swap。
swap的实现:a先调用拷贝构造函数创建temp,通过拷贝赋值函数将b赋给a,再通过赋值函数将temp赋给b;
但对于另一种类的定义:"以指针指向一个对象,内含真正数据"那种类型,这种设计的常见表现形式是所谓"pimpl 手法",即把数据寄托在另外一个类中。如果采用默认的swap函数,会出现什么状况?
class WigetImpl { public: WigetImpl(const int v) : m_value(v){ } int GetValue() const {return m_value;} private: int m_value; }; class Wiget { public: Wiget(const int v) {m_impl = new WigetImpl(v);} ~Wiget() {if (m_impl != NULL) delete m_impl; m_impl = NULL;} Wiget(const Wiget &wiget) { this->m_impl = new WigetImpl(wiget.m_impl->GetValue()); } Wiget& operator = (const Wiget &wiget) { if (this != &wiget) { //采用copy-and-swap技术会更好,参考前面条款 *m_impl = *wiget.m_impl; } return *this; } private: WigetImpl *m_impl; };调用
Wiget w1(2); Wiget w2(3); std::swap(w1, w2);调用缺省的swap,不但需要调用三次Wiget的copy函数,还调用三次WigetImpl的copy函数,效率非常低。
我们希望能够告诉std:: swap: 当Widgets 被置换时真正该做的是置换其内部的m_impl指针。确切实践这个思路的一个做法是将std: :swap 针对Widget 特化,即全特化。
namespace std { template<> void swap<Wiget>(Wiget &a,Wiget &b) { a.swap(b); //调用Wiget的swap函数 } }在Wiget中定义一个swap函数,实现对m_impl指针交换
void swap(Wiget & rhl) { std::swap(this->m_impl, rhl.m_impl); }这样调用只是调用三次WigetImpl的copy函数。
假设Wiget和WigetImpl都是一个类模板,如下面
template<typename T> class WigetImpl {}; template<typename T> class Wiget{};我们对函数进行偏特化
namespace std { template<typename T> void swap<Wiget<T> >(Wiget<T> &lhs, Wiget<T> &rhs) { lhs.swap(rhs); } }但C++只对类偏特化,函数会报错,如:“std::swap”: 非法使用显式模板参数
解决函数偏特化问题,即修改为函数重载,如下:
namespace std { template<typename T> void swap(Wiget<T> &lhs, Wiget<T> &rhs) { lhs.swap(rhs); } }但std是个特殊的命名空间,可以全特化std内的template,但不能添加新的template到std。
这些,都可以通到定义一个no-member function解决,不属于std的命名空间
template<typename T> void swap(Wiget<T> &lhs, Wiget<T> &rhs) { lhs.swap(rhs); //调用Wiget的swap函数 }上面介绍了3中swap:
①std的缺省swap
②std的特化版本
③class命名空间的no-member swap
那么它们的调用顺序是怎么样呢?为了保证调用顺序是③②①,需要在函数中声明std命名空间
template<typename T> void CallSwap(T &obj1, T &obj2) { <span style="white-space:pre"> </span>using std::swap; <span style="white-space:pre"> </span>swap(obj1, obj2); }相关的原理,可以查看C++名称查找法则
总结swap的使用规则:
首先,如果swap的缺省实现为你的类或类模板提供了可接受的性能,你不需要做任何事。任何试图交换类型的对象的操作都会得到缺省版本的支持,而且能工作得很好。
第二,如果swap缺省实现效率不足(这几乎总是意味着你的类或模板使用了某种pimpl手法),就按照以下步骤来做:
1.提供一个public的swap成员函数,能高效地交换你的类型的两个对象值,这个函数应该永远不会抛出异常。
2.在你的类或模板所在的同一个namespace中,提供一个非成员的swap,用它调用你的swap成员函数。
3.如果你写了一个类(不是类模板),为你的类特化std::swap,并令它调用你的swap 成员函数。
最后,如果你调用swap,确保在你的函数中包含一个using 声明式使std::swap可见,然后在调用swap时不使用任何namespace修饰符
第二,如果swap缺省实现效率不足(这几乎总是意味着你的类或模板使用了某种pimpl手法),就按照以下步骤来做:
1.提供一个public的swap成员函数,能高效地交换你的类型的两个对象值,这个函数应该永远不会抛出异常。
2.在你的类或模板所在的同一个namespace中,提供一个非成员的swap,用它调用你的swap成员函数。
3.如果你写了一个类(不是类模板),为你的类特化std::swap,并令它调用你的swap 成员函数。
最后,如果你调用swap,确保在你的函数中包含一个using 声明式使std::swap可见,然后在调用swap时不使用任何namespace修饰符
绝不要让swap的成员版本抛出异常。这是因为swap非常重要的应用之一是为类(以及类模板)提供强大的异常安全(exception-safety)保证。
贴出最终的测试代码
template<typename T> class WigetImpl { public: WigetImpl(const T v) : m_value(v){ } T GetValue() const {return m_value;} private: T m_value; }; template<typename T> class Wiget { public: Wiget(const T v) {m_impl = new WigetImpl<T>(v);} ~Wiget() {if (m_impl != NULL) delete m_impl; m_impl = NULL;} Wiget(const Wiget &wiget) { this->m_impl = new WigetImpl<T>(wiget.m_impl->GetValue()); } Wiget& operator = (const Wiget &wiget) { if (this != &wiget) { //采用copy-and-swap技术会更好,参考前面条款 *m_impl = *wiget.m_impl; } return *this; } void swap(Wiget & rhl) { std::swap(this->m_impl, rhl.m_impl); } private: WigetImpl<T> *m_impl; }; namespace std { template<> void swap<Wiget<int> >(Wiget<int> &a,Wiget<int> &b) { a.swap(b); //调用Wiget的swap函数 } } template<typename T> void swap(Wiget<T> &lhs, Wiget<T> &rhs) { lhs.swap(rhs); } template<typename T> void CallSwap(T &obj1, T &obj2) { using std::swap; swap(obj1, obj2); }
记住
①当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常.
②如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),
也请特化std::swap.
③调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何""命名空间资格修饰.
④为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西.
②如果你提供一个member swap,也该提供一个non-member swap用来调用前者.对于classes(而非template),
也请特化std::swap.
③调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何""命名空间资格修饰.
④为"用户定义类型"进行std templates全特化是好的,但千万不要尝试在std内加入某些std而言全新的东西.
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。