【原创】C++之自定义高效的swap(1)

1 问题背景

当交换两个包含了指针成员的类,我们最想看到的是直接交换其指针。但是当我们调用std::swap标准库这个模板函数时,通常它都会复制3个指针指向的对象作为交换所用,缺乏效率。如下:

1 namespace std{
2     template<typename T>
3     void swap(T& a, T& b) //std::swap的典型实现
4     {
5         T temp(a);    //一次拷贝,两次赋值
6         a = b;
7         b = temp;
8     }
9 }

上面的代码,5行的调用了类的拷贝构造函数将a拷贝给temp,6、7行调用了拷贝赋值函数交换a、b对象。

那么我们能不能自定义一个较高效率的属于我们自己类的swap函数呢?

2 自定义高效的swap函数

我们可以为自己写的新类T提供一个高效的swap方法。一般来说,提供swap方法有两种。

2.1 swap成员函数

2.1.1 方法

(1)在我们写的类T中添加一个swap成员函数,这样方便我们交换类中的私有成员,并且设置swap函数不会抛出异常,为什么?见《C++ Primer 第五版中文版》474页。

void T::swap(T& t) noexcept;

(2)在类T的同一命名空间里添加一个非成员函数swap,用于调用类中的成员函数swap

1 void swap(T& a, T& b) noexcept
2 {
3     a.swap(b);
4 }

2.1.2 典型实现

 1 #include <iostream>
 2 #include <string>
 3 class ClassTest{
 4 public:
 5     friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
 6     friend void swap(ClassTest &a, ClassTest &b) noexcept;
 7     ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
 8     ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
 9     ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
10     {
11         str = new std::string(*ct.str);
12         return *this;
13     }
14     ~ClassTest() //析构函数
15     {
16         delete str;
17     }
18     void swap(ClassTest &t) noexcept
19     {
20         using std::swap;
21         swap(str,t.str); //交换指针,而不是string数据
22     }
23 private:
24     std::string *str;  //一个指针资源
25 };
26 std::ostream& operator<<(std::ostream &os,const ClassTest& s)
27 {
28     os << *s.str;
29     return os;
30 }
31 void swap(ClassTest &a, ClassTest &b) noexcept
32 {
33     a.swap(b);
34 }
35 int main(int argc, char const *argv[])
36 {
37     ClassTest ct1("ct1");
38     ClassTest ct2("ct2");
39     std::cout << ct1 << std::endl;
40     std::cout << ct2 << std::endl;
41     swap(ct1, ct2);
42     std::cout << ct1 << std::endl;
43     std::cout << ct2 << std::endl;
44     return 0;
45 }

注意:

  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样。
  3. (2)中的函数可以声明为类T的友元函数,并且设置为内联函数
  4. 做真实交换的swap函数,需要使用using std::swap;

2.1.2 关于using std::swap

1 void swap(ClassTest &t) noexcept
2 {
3     using std::swap;
4     swap(str, t.str); //交换指针,而不是string数据
5 }

在这里为什么要使用using std::swap呢?也就是using std::swap的作用是什么?

在C++里存在多种名字查找规则:

  1. 普通的名字查找:先在当前的作用域里查找,查找不到再到外层作用域中查找,即局部相同名字的变量或函数会隐藏外层的变量或作用域
  2. 实参相关的名字查找(ADL)(链接2):当函数的形参是类类型时,首先在当前的作用域中寻找合适的函数,接着查找外层作用域的函数,最后还会查找形参类类型所属的命名空间
  3. 涉及函数模板匹配规则:一个调用的候选函数(关于候选函数请参考C++ Primer第五版中关于函数的一章)包括所有模板实参推断成功的函数模板实例;候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板;如果恰好有一个函数(或模板)比其他函数更加匹配,则选择该函数;同样好的函数里对于有多个函数模板和只有一个非模板函数,会优先选择非模板函数;同样好的函数里对于没有非模板函数,那么选择更特例化的函数模板
  4. 因为C++会优先在当前的作用域里查找,所以使用using std::swap将标准库的swap模板函数名字引入该局部作用域,重载当前作用域的同名函数,隐藏外层作用域的相关声明。为什么using std::swap不会隐藏外层的void swap(ClassTest &a, ClassTest &b) noexcept函数呢?参见:这里。其中说到,当经过普通的名字查找后(没有包括ADL),如果候选函数中有类成员、块作用域中的函数声明(不包括using声明引入的)、其他同名的函数对象或变量名,则不启动ADL查找了。如果没有,则进行ADL查找。因此在经过普通的查找后,发现并没有匹配的函数,最后再经过ADL找到了标准库中的swap和外层作用域的void swap(ClassTest &a, ClassTest &b) noexcept,由于后者较匹配,编译器优先选择后者。
  5. 如果str类型有自定义的swap函数,那么第4行代码的swap调用将会调用str类型自定义的swap函数
  6. 但是如果str类型并没有特定的swap函数,那么第4行代码的swap调用将会被解析到标准库的std::swap

2.2 swap友元函数

2.2.1 方法

(1)在T的同一命名空间中定义一非成员的swap函数,并且将函数声明为类T的友元函数,方便访问T的私有成员

1 void swap(ClassTest &a, ClassTest &b) noexcept
2 {
3     using std::swap;
4     //swap交换操作
5 }

2.2.2 典型实现

 1 #include <iostream>
 2 #include <string>
 3 class ClassTest{
 4 public:
 5     friend std::ostream& operator<<(std::ostream &os, const ClassTest& s);
 6     friend void swap(ClassTest &a, ClassTest &b) noexcept;
 7     ClassTest(std::string s = "abc") :str(new std::string(s)){} //默认构造函数
 8     ClassTest(const ClassTest &ct) :str(new std::string(*ct.str)){} //拷贝构造函数
 9     ClassTest &operator=(const ClassTest &ct) //拷贝赋值函数
10     {
11         str = new std::string(*ct.str);
12         return *this;
13     }
14     ~ClassTest() //析构函数
15     {
16         delete str;
17     }
18 private:
19     std::string *str;  //一个指针资源
20 };
21 std::ostream& operator<<(std::ostream &os, const ClassTest& s)
22 {
23     os << *s.str;
24     return os;
25 }
26 void swap(ClassTest &a, ClassTest &b) noexcept
27 {
28     using std::swap;
29     swap(a.str,b.str); //交换指针,而不是string数据
30 }
31 int main(int argc, char const *argv[])
32 {
33     ClassTest ct1("ct1");
34     ClassTest ct2("ct2");
35     std::cout << ct1 << std::endl;
36     std::cout << ct2 << std::endl;
37     swap(ct1, ct2);
38     std::cout << ct1 << std::endl;
39     std::cout << ct2 << std::endl;
40     return 0;
41 }

注意:

  1. (2)中的swap函数需要和类T位于同一的命名空间里,否则外部调用swap函数可能会解析不到
  2. swap函数最好使它不要抛出异常,就像移动构造函数和移动赋值函数一样

本文链接:【原创】C++之swap(1)http://www.cnblogs.com/cposture/p/4939971.html

时间: 2024-10-08 09:19:55

【原创】C++之自定义高效的swap(1)的相关文章

高效的swap

原始版本: template<typename T> void swap(T& a, T& b) { T tmp(a); a = b; b = tmp; } 此版本不重视效率,当交换的两个对象比较大时,需要更高效的交换,因此应该提供1)public swap成员函数,让它高效的置换两个对象,并提供nono-member swap,调用之 ///////////////////////////////////////////////////////////////////////

[原创]CACTI中自定义华为交换机CPU利用率 (图)

http://bbs.chinaunix.net/thread-2201413-1-1.html

swap function &amp; copy-and-swap idiom

在C++中,众所周知在一个资源管理类(例如含有指向堆内存的指针)中需要重新定义拷贝构造函数.赋值运算符以及析构函数(Big Three),在新标准下还可能需要定义移动构造函数和移动赋值运算符(Big Five).但实际上,这条规则还可以有一个小扩展.就是在资源管理类中,往往需要重新定义自己的swap函数来作为优化手段. 1. swap函数 首先考察如下例子,假设类HasPtr中含有一个指向string的指针 *ps 和一个int类型值value. class HasPtr { public: .

《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++》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 =

swap() 函数实现的方法

swap()函数总结: 一.利用临时变量 1.引用(交换任意类型) template <typename T> void swap(T& x,T& y) { T tmp; tmp = y; y = x; x = tmp; } 2.泛型指针() void swap(void* a,void* b ) { int tmp; tmp = y; y = x; x = tmp; } 二. 不用临时变量交换 1.数学运算 1)乘 void swap (int& x,int&

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++读书笔记(条款24-29)

Effective C++第四篇,扩展的有点多... (四).设计与声明 ____________________________________________________________________________________________________________________________________ 条款24:若所有参数皆需类型转换,请为此采用non-member函数 #1.如果你需要为某个函数的所有参数(包括被 this指针所指的那个隐喻参数)进行 类型转

Effictive C++知识点复习

1.尽量以const.enum.inline替换#define或者宁可以编译器替换预处理器eg:#define NUM_RATIO 1.653由于NUM_RATIO在编译器开始处理源码之前都被预处理器移走,因而当常量在编译时出错,只会提示到1.653.对于程序员并不知道1.653在哪个文件中存放.故追踪会浪费时间.即所使用的名称并未进入记号表中.解决方法:用一个常量替换上面的宏const double NumRatio = 1.653;注意:两个常量定义时的写法2.若在头文件定义一个常量的字符串