条款25:考虑写出一个不抛出异常的swap函数
2015-10-09 09:46
483 查看
首先说下标准库的swap算法:
显然的,标准库实在是索然无味,自己随便写都能写出来,而且这样写有时候对于某些来说效率是特别低的。看看下面这个这个例子:
这个operator=配上上面那个swap一起的效率便非常的地下,本来交换Widget中Impl两者的指针就可以很高效的完成swap操作,但是这里硬是活生生的拷贝了两个对象。
那么怎么完成高效的swap函数呢,首先想到的可能就是将标准库中的swap特例化,像下面这样:
上面这个swap是标准库swap的total template specialization版本,但是他并不能通过编译,原因是因为其访问了对象的私有函数。正确的做法一般是下面这样:
上面的做法与STL标准库也是高度一致,标准库的容器也都是有std版本的swap而且都在自己内部空间实现了swap供给前者调用。
但是上述的做法在Widget是类模板的时候就不起作用了。因为下面的式子:
被c++规定为非法的。
所以所,在无法进行模板特例化的情况下,就需要在特定的自己定义的名字空间创造一个swap函数(或者在std空间中重载一个版本,但是c++是不允许那样做的)。那么据类似下面这样:
这样就大功告成了。。
这样,在任何的位置调用有关Widget的swap的时候,就会首先在WidgetStuff的名字空间里面寻找相应的swap。
注意Widget的成员函数版本的swap中有一行是加上了下划线的,这个using声明语句的作用是即使当这个名字空间中的swap对Impl不能起到作用的时候,至少可以让Impl可以借助于std::swap来完成交换的操作。
总结一下:
如果因为系统默认版本的swap效率不足话,那么就要做下面这几件事:
1. 提供一个class public swap对象,使其高效的置换两个特殊对象的值
2. 在这个class或者template所在的命名空间提供一个non-memberswap,作用是来调用上述的
swap.
3.如果上述的class或者class template实际上是个class而非template,最好是特例化std::swap
,并且用这个来调用member的swap函数。
4.在使用swap的时候,尤其是在自己定义的命名空间下,记得使用一个using 声明吧std::swap包含进来,这样即使在本命名空间中找不到合适的swap,至少也可以调用std的swap.且使用swap的时候不要再前面加上名字空间声明,这样前面声明的std::swap就白做了
注意,成员版本的swap绝对不能抛出异常,但是非成员版本的swap函数是允许抛出异常的。前者不抛出异常的原因是其往往是针对内置类型的操作,而内置类型的操绝对不允许抛出异常。
namespace std{ template<typename T> void swap(T & a, T & b) { T tmp = a; a = b; b = tmp; } }
显然的,标准库实在是索然无味,自己随便写都能写出来,而且这样写有时候对于某些来说效率是特别低的。看看下面这个这个例子:
class WidgetImpl{ public: ... private: int a, b, c; std::vector<double> v; ... }; class Widget{ pubcic: Widget(const Widget & rhs); Widget & operator=(const Widget & rhs) { ... *pImpl = *(rhs.pImpl); ... } private: WidgteImpl * pImpl; };
这个operator=配上上面那个swap一起的效率便非常的地下,本来交换Widget中Impl两者的指针就可以很高效的完成swap操作,但是这里硬是活生生的拷贝了两个对象。
那么怎么完成高效的swap函数呢,首先想到的可能就是将标准库中的swap特例化,像下面这样:
namespace std{ template<> void swap<Widget>(Widget & a, Widget & b) { swap(a.Impl, b.Impl); } }
上面这个swap是标准库swap的total template specialization版本,但是他并不能通过编译,原因是因为其访问了对象的私有函数。正确的做法一般是下面这样:
class Widget{ public: ... void swap(Widget & other){ using std::swap; swap(pUmpl, other.pUmpl); } ... }; namespace std{ template<> void swap<Widget>(Widget & a, Widget & b) { a.swap(b); } }
上面的做法与STL标准库也是高度一致,标准库的容器也都是有std版本的swap而且都在自己内部空间实现了swap供给前者调用。
但是上述的做法在Widget是类模板的时候就不起作用了。因为下面的式子:
namespace std{ template<typename T> void swap<Widget<T> >(Widget<T> & a, Widget<T> & b) { a.swap(b); } }
被c++规定为非法的。
所以所,在无法进行模板特例化的情况下,就需要在特定的自己定义的名字空间创造一个swap函数(或者在std空间中重载一个版本,但是c++是不允许那样做的)。那么据类似下面这样:
namespace WidgetStuff{ ... template<typename T> class Widget{.......}; ... template <typename T> void swap(Widget <T> & a, Widget<T> & b) { a.swap(b); } }
这样就大功告成了。。
这样,在任何的位置调用有关Widget的swap的时候,就会首先在WidgetStuff的名字空间里面寻找相应的swap。
注意Widget的成员函数版本的swap中有一行是加上了下划线的,这个using声明语句的作用是即使当这个名字空间中的swap对Impl不能起到作用的时候,至少可以让Impl可以借助于std::swap来完成交换的操作。
总结一下:
如果因为系统默认版本的swap效率不足话,那么就要做下面这几件事:
1. 提供一个class public swap对象,使其高效的置换两个特殊对象的值
2. 在这个class或者template所在的命名空间提供一个non-memberswap,作用是来调用上述的
swap.
3.如果上述的class或者class template实际上是个class而非template,最好是特例化std::swap
,并且用这个来调用member的swap函数。
4.在使用swap的时候,尤其是在自己定义的命名空间下,记得使用一个using 声明吧std::swap包含进来,这样即使在本命名空间中找不到合适的swap,至少也可以调用std的swap.且使用swap的时候不要再前面加上名字空间声明,这样前面声明的std::swap就白做了
注意,成员版本的swap绝对不能抛出异常,但是非成员版本的swap函数是允许抛出异常的。前者不抛出异常的原因是其往往是针对内置类型的操作,而内置类型的操绝对不允许抛出异常。
相关文章推荐
- ROS_添加packge
- Linux伙计专用Hello World Java
- javascirpt中==和===的区别
- 计算机图形学:基于3D游戏开发——第二章 顶点处理机制
- 字符串移位
- CALayer绘图
- 上传不了文件
- 新浪微博生成超短链接
- JavaScript实现横向滑出的多级菜单效果
- 自动化批量安装系统
- 任意输入数学表达式,绘制数学函数图形
- 培训总结
- 小明的程序员之路
- List转JSON字符串工具类
- 项目之间的添加引用问题
- X3850M2安装CertOS 7 KVM 2--VNC
- iOS App开发那些事:如何选择合适的人、规范和框架?
- Git 命令 冲突
- vi/vim滚动屏幕
- Struts2国际化支持(中英文选择)