【原創】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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM