1. 程式人生 > >談談C++中的swap函式

談談C++中的swap函式

1,最通用的模板交換函式模式:建立臨時物件,呼叫物件的賦值操作符

[cpp] view plain copy print?
  1. template <class T> void swap ( T& a, T& b )  
  2. {  
  3.   T c(a); a=b; b=c;  
  4. }  
 

需要構建臨時物件,一個拷貝構造,兩次賦值操作。

2,針對int型優化:

[cpp] view plain copy print?
  1. void swap(int & __restrict a, int & __restrict b)  
  2. {  
  3. a ^= b;  
  4. b ^= a;  
  5. a ^= b;  
  6. }  
 

無需構造臨時物件,異或

因為指標是int,所以基於這個思路可以優化1:

[cpp] view plain copy print?
  1. template <typename T> void Swap(T & obj1,T & obj2)  
  2. {  
  3.     unsigned char * pObj1 = reinterpret_cast<unsigned char *>(&obj1);  
  4.     unsigned char * pObj2 = reinterpret_cast<unsigned 
    char *>(&obj2);  
  5.     for (unsigned long x = 0; x < sizeof(T); ++x)  
  6.     {  
  7.         pObj1[x] ^= pObj2[x];  
  8.         pObj2[x] ^= pObj1[x];  
  9.         pObj1[x] ^= pObj2[x];  
  10.     }  
  11. }  
 

3,針對內建型別的優化:  int, flaot, double 等,甚至過載運算子的使用者自定義型別:向量,矩陣,影象等。。。

type  a; -- e.g 10
type  b; -- e.g 5

a = a+b ; -- a=15,b=5
b = a-b ; -- a=15,b=10
a= a -b ; -- a= 5,b=10

// 無需構造臨時變數。使用基本運算操作符。

[cpp] view plain copy print?
  1. Ok, let's see.  
  2. a = a + b;  
  3. b = a - b;  
  4. a = a - b;  
  5. Let's introduce new names  
  6. c = a + b;  
  7. d = c - b;  
  8. e = c - d;  
  9. And we want to prove that d == a and e == b.  
  10. d = (a + b) - b = a, proved.  
  11. e = (a + b) - ((a + b) - b) = (a + b) - a = b, proved.  
  12. For all real numbers.  
 

4,swap的一些特化:

std::string, std::vector各自實現了swap函式,

string

[cpp] view plain copy print?
  1. template<class _Elem,  
  2.     class _Traits,  
  3.     class _Alloc> inline
  4.     void __CLRCALL_OR_CDECL swap(basic_string<_Elem, _Traits, _Alloc>& _Left,  
  5.         basic_string<_Elem, _Traits, _Alloc>& _Right)  
  6.     {   // swap _Left and _Right strings
  7.     _Left.swap(_Right);  
  8.     }  
  9.     void __CLR_OR_THIS_CALL swap(_Myt& _Right)  
  10.         {   // exchange contents with _Right
  11.         if (this == &_Right)  
  12.             ;   // same object, do nothing
  13.         elseif (_Mybase::_Alval == _Right._Alval)  
  14.             {   // same allocator, swap control information
  15.  #if _HAS_ITERATOR_DEBUGGING
  16.             this->_Swap_all(_Right);  
  17.  #endif /* _HAS_ITERATOR_DEBUGGING */
  18.             _Bxty _Tbx = _Bx;  
  19.             _Bx = _Right._Bx, _Right._Bx = _Tbx;  
  20.             size_type _Tlen = _Mysize;  
  21.             _Mysize = _Right._Mysize, _Right._Mysize = _Tlen;  
  22.             size_type _Tres = _Myres;  
  23.             _Myres = _Right._Myres, _Right._Myres = _Tres;  
  24.             }  
  25.         else
  26.             {   // different allocator, do multiple assigns
  27.             _Myt _Tmp = *this;  
  28.             *this = _Right;  
  29.             _Right = _Tmp;  
  30.             }  
  31.         }  
 

第二個swap(Right)進行判斷,如果使用了相同的分配器,則直接交換控制資訊,否則呼叫string::operator=進行拷貝賦值。。。所以建議優先使用swap函式,而不是賦值操作符。

vector

[cpp] view plain copy print?
  1. template<class _Ty,  
  2.     class _Alloc> inline
  3.     void swap(vector<_Ty, _Alloc>& _Left, vector<_Ty, _Alloc>& _Right)  
  4.     {   // swap _Left and _Right vectors
  5.     _Left.swap(_Right);  
  6.     }  
  7.     void swap(_Myt& _Right)  
  8.         {   // exchange contents with _Right
  9.         if (this == &_Right)  
  10.             ;   // same object, do nothing
  11.         elseif (this->_Alval == _Right._Alval)  
  12.             {   // same allocator, swap control information
  13.  #if _HAS_ITERATOR_DEBUGGING
  14.             this->_Swap_all(_Right);  
  15.  #endif /* _HAS_ITERATOR_DEBUGGING */
  16.             this->_Swap_aux(_Right);  
  17.             _STD swap(_Myfirst, _Right._Myfirst);  
  18.             _STD swap(_Mylast, _Right._Mylast);  
  19.             _STD swap(_Myend, _Right._Myend);  
  20.             }  
  21.         else
  22.             {   // different allocator, do multiple assigns
  23.             this->_Swap_aux(_Right);  
  24.             _Myt _Ts = *this;  
  25.             *this = _Right;  
  26.             _Right = _Ts;  
  27.             }  
  28.         }  

vector的swap原理跟string完全一致,只有噹噹使用了不同分配器才進行位元組拷貝。其餘情況直接交換控制資訊。

測試用例:

5,Copy and  Swap idiom

目的:C++異常有三個級別:基本沒有異常。通過建立臨時物件然後交換,能夠實現過載賦值操作符的強異常安全的執行。

Loki中智慧指標 臨時變數跟this交換,臨時變數自動銷燬~

[cpp] view plain copy print?
  1. SmartPtr& operator=(SmartPtr<T1, OP1, CP1, KP1, SP1, CNP1 >& rhs)  
  2. {  
  3.     SmartPtr temp(rhs);  
  4.     temp.Swap(*this);  
  5.     return *this;  
  6. }  
 

boost::share_ptr,share_ptr定義了自己的swap函式。

[cpp] view plain copy print?
  1. shared_ptr & operator=( shared_ptr const & r ) // never throws
  2. {  
  3.     this_type(r).swap(*this);  
  4.     return *this;  
  5. }  
  6. void swap(shared_ptr<T> & other) // never throws
  7. {  
  8.     std::swap(px, other.px);  
  9.     pn.swap(other.pn);  
  10. }  
 

記得本科上C++課,老師特別喜歡拿String來舉例子,面試題也特別喜歡String。。。下面說說String::opreator=函式的優化:

最一般的寫法,特點:使用const string& 傳參防止臨時物件。

[cpp] view plain copy print?
  1. String& String::operator =(const String & rhs)  
  2. {  
  3.     if (itsString)  
  4.         delete [] itsString;  
  5.     itsLen = rhs.GetLen();  
  6.     itsString = newchar[itsLen+1];  
  7.     for (unsigned short i = 0;i<itsLen;i++)  
  8.         itsString[i] = rhs[i];  
  9.     itsString[itsLen] = '/0';  
  10.     return *this;  
  11. }  
 

優化1,防止自我間接賦值,a = b; c = b; a = c; 如果沒有第一個if判斷,當把c賦給a的時候,刪除了a.itsString,後面的拷貝就會出錯。注意是if(this==&rhs), 而不是if(*this==rhs).

[cpp] view plain copy print?
  1. String& String::operator =(const String & rhs)  
  2. {  
  3.     if (this == &rhs)  
  4.         return *this;  
  5.     if (itsString)  
  6.         delete [] itsString;  
  7.     itsLen=rhs.GetLen();  
  8.     itsString = newchar[itsLen+1];  
  9.     for (unsigned short i = 0;i<itsLen;i++)  
  10.         itsString[i] = rhs[i];  
  11.     itsString[itsLen] = '/0';  
  12.     return *this;  
  13. }  
 

優化2,不進行拷貝賦值,只是交換控制資訊,而且是強異常安全:

[cpp] view plain copy print?
  1. String & String::operator = (String const &rhs)  
  2. {  
  3.     if (this != &rhs)  
  4.         String(rhs).swap (*this); // Copy-constructor and non-throwing swap
  5.     // Old resources are released with the destruction of the temporary above
  6.     return *this;  
  7. }  
 

優化3,以最原始的傳值方式傳參,避免臨時物件建立:

[cpp] view plain copy print?
  1. String & operator = (String s) // the pass-by-value parameter serves as a temporary
  2. {  
  3.    s.swap (*this); // Non-throwing swap
  4.    return *this;  
  5. }// Old resources released when destructor of s is called.
 

 6. vector clear and swap trick

vector.clear並只是將size變數置為0,並沒有及時歸還OS,STL仍然持有記憶體,以便後續push_back。實測如下

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. vector<int> temp;  
此時開啟資源管理器,記憶體如下:


增長vector然後清空:

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. temp.resize( 1024*1024*20 );    // 80M
  2. temp.clear();  
此時資源管理器記憶體:

clear以後程序兵沒有及時將記憶體歸還OS。。。通過swap方法:

[cpp] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. tmp.resize(1024*1024*20);    // 80M
  2. // tmp.clear();
  3. {  
  4.      std::vector<int>().swap(tmp);        // 將記憶體歸還OS
  5. }  
退出作用域,臨時物件銷燬。記憶體歸還OS。此時資源管理器中程序記憶體回到1,864K。

附上網路版的String:

[cpp] view plain copy print?
  1. #include <iostream>
  2. #include <cstring>
  3. usingnamespace std;  
  4. class String  
  5. {  
  6.         public:  
  7.                 String();  
  8.                 String(constchar *const);  
  9.                 String(const String &);  
  10.                 ~String();  
  11.                 char & operator[] (unsigned short offset);  
  12.                 char operator[] (unsigned short offset)const;  
  13.                 String operator+(const String&);  
  14.                 void operator+=(const String&);  
  15.                 String & operator= (const String &);  
  16.                 unsigned short GetLen()const {return itsLen;}  
  17.                 constchar * GetString()const {return itsString;}  
  18.         private:  
  19.                 String (unsigned short);  
  20.                 char * itsString;  
  21.                 unsigned short itsLen;  
  22. };  
  23. String::String()  
  24. {  
  25.         itsString = newchar[1]; //為什麼設定成1,這樣會導致記憶體1bytes無法釋放嗎?我覺得和itsString = new char沒區別,那他為什麼要設定成1,這樣有什麼用?21天學會C++那本書,我也有 ,書上也確實是設定成1.
  26.         itsString[0] = '/0';  
  27.         itsLen=0;  
  28. }  
  29. String::String(unsigned short len)  
  30. {  
  31.         itsString = newchar[len+1];  
  32.         for (unsigned short i =0;i<=len;i++)  
  33.                 itsString[i] = '/0';  
  34.         itsLen=len;  
  35. }  
  36. String::String(constchar * const cString)  
  37. {  
  38.         itsLen = strlen(cString);  
  39.         itsString = newchar[itsLen+1];  
  40.         for (unsigned short i=0;i<itsLen;i++)  
  41.                 itsString[i] = cString[i];  
  42.         itsString[itsLen] = '/0';  
  43. }  
  44. String::String(const String & rhs)  
  45. {  
  46.         itsLen = rhs.GetLen();  
  47.         itsString = newchar[itsLen+1];  
  48.         for (unsigned short i = 0;i<itsLen;i++)  
  49.                 itsString[i] = rhs[i];  
  50.         itsString[itsLen] = '/0';  
  51. }  
  52. String::~String()  
  53. {  
  54.         delete [] itsString;  
  55.         itsLen = 0;  
  56. }  
  57. String& String::operator =(const String & rhs)  
  58. {  
  59.         if (this == &rhs)  
  60.                 return *this;  
  61.         delete [] itsString;  
  62.         itsLen=rhs.GetLen();  
  63.         itsString = newchar[itsLen+1];  
  64.         for (unsigned short i = 0;i<itsLen;i++)  
  65.                 itsString[i] = rhs[i];  
  66.         itsString[itsLen] = '/0';  
  67.         return *this;  
  68. }  
  69. char & String::operator [](unsigned short offset) //這個程式這樣寫,起到了什麼用處??和main中的那一個對應?
  70. {  
  71.         if (offset > itsLen)  
  72.                 return itsString[itsLen-1]; //這個返回itslen-1到底是什麼意思?為什麼要減去1 ??
  73.         else
  74.                 return itsString[offset];  
  75. }  
  76. char String::operator [](unsigned short offset)const
  77. {  
  78.         if (offset > itsLen)  
  79.                 itsString[itsLen-1];  
  80.         else
  81.                 return itsString[offset];  
  82. }  
  83. String String::operator +(const String& rhs)  
  84. {  
  85.         unsigned short totalLen = itsLen + rhs.GetLen();  
  86.         String temp(totalLen);  
  87.         unsigned short i;  
  88.         for (i=0;i<itsLen;i++)  
  89.                 temp[i] = itsString[i];  
  90.         for (unsigned short j = 0;j<rhs.GetLen();j++,i++)  
  91.                 temp[i] = rhs[j];  
  92.         temp[totalLen] = '/0';  
  93.         return temp;  
  94. }  
  95. void String::operator +=(const String& rhs)  
  96. {  
  97.         unsigned short rhsLen = rhs.GetLen();  
  98.         unsigned short totalLen = itsLen + rhsLen;  
  99.         String temp(totalLen);  
  100.         unsigned short i;  
  101.         for (i = 0;i<itsLen;i++)  
  102.                 temp[i] = itsString[i];  
  103.         for (unsigned short j = 0;j<rhs.GetLen();j++,i++)  
  104.                 temp[i] = rhs[i-itsLen];  
  105.         temp[totalLen] = '/0';  
  106. }  
  107. int main()  
  108. {  
  109.         String s1("initial test"); //呼叫了什麼函式?
  110.         cout<<"S1:/t"<<s1.GetString()<<endl;  
  111.         char *temp ="Hello World";  
  112.         s1 = temp;//呼叫了什麼函式?
  113.         cout<<"S1:/t"<<s1.GetString()<<endl;  
  114.         char tempTwo[20];  
  115.         strcpy(tempTwo,"; nice to be here!");  
  116.         s1 += tempTwo;  
  117.         cout<<"tempTwo:/t"<<tempTwo<<endl;  
  118.         cout<<"S1:/t"<<s1.GetString()<<endl;  
  119.         cout<<"S1[4]:/t"<<s1[4]<<endl;  
  120.         cout<<"S1[999]:/t"<<s1[999]<<endl;//呼叫了什麼函式?
  121.         String s2(" Anoter string");//呼叫了什麼函式?
  122.         String s3;  
  123.         s3 = s1+s2;  
  124.         cout<<"S3:/t" <<s3.GetString()<<endl;  
  125.         String s4;  
  126.         s4 = "Why does this work?";//呼叫了什麼函式?
  127.         cout<<"S4:/t"<<s4.GetString()<<endl;  
  128.         return 0;  
  129. }  

參考引用:

C++ idioms

Copy and Swap idiom

History:

20140401 - add 6 vector clear and swap trick!