《Effective C++》item25:考虑写出一个不抛异常的swap函数
?std::swap()是個很有用的函數(shù),它可以用來交換兩個變量的值,包括用戶自定義的類型,只要類型支持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 = 0; i < vec1.size(); i++) { cout<<vec1[i]<<" "; } return 0; }?
? ? ? ? 上面這個例子實現(xiàn)的是兩個vector的內(nèi)容的交換,有了swap函數(shù),省去了很多的麻煩!What a fucking convenient!
?
?
一、swap的原理
?
? ? ? ? 缺省的swap的原理其實很簡單,就是將兩對象的值彼此賦予對方,其實現(xiàn)過程大致如下:
?
namespace std{ template<typename T> void swap(T&a, T& b){ T temp(a); a=b; b=temp; } } ?? ? ? ??swap的實現(xiàn)是通過被交換類型的copy構(gòu)造函數(shù)和賦值操作符重載實現(xiàn)的,會涉及到三個對象的復(fù)制。所以說,要對自定義的類型調(diào)用swap實現(xiàn)交換,必須首先保證自定義類型的copy構(gòu)造函數(shù)和賦值操作符重載函數(shù)。
?
?
二、swap的缺陷
?
? ? ? ? 缺省的swap最主要的問題就是:當(dāng)對象內(nèi)部包含指針成員時,它不僅要復(fù)制3三次被交換的對象,還要復(fù)制3次對象成員,而且復(fù)制的是指針對象所指向的內(nèi)容!例如:
// SwapTest.cpp : 定義控制臺應(yīng)用程序的入口點。 #include "stdafx.h" #include <iostream> #include <vector> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; Point(int a, int b):x(a),y(b){}; void Print(){ cout<< x << " "<< y <<endl; } int GetX(){ return x; } int GetY(){ return y; } }; class Line{ private: Point *px, *py; public: Line():px(),py(){}; Line(int a,int b,int c,int d):px(new Point(a,b)), py(new Point(c, d)){}; Line(const Line& li){ px = new Point(*li.px); py = new Point(*li.py); } Line& operator=(const Line& li){ Point *p = px; px = new Point(*li.px); delete p; p = py; py = new Point(*li.py); delete p; return *this; } void Print(){ cout<<"( "<<px->GetX()<<" "<<px->GetY()<<" "<<py->GetX()<<" "<<py->GetY()<<" )"<<endl; } }; int main(int argc, _TCHAR* argv[]) { Line l1(1,1,2,2), l2(3,3,4,4); swap(l1, l2); l1.Print(); return 0; }?
? ? ? ? 一旦要置換兩個Line對象值,swap需要復(fù)制三個Line,還要復(fù)制六個Point對象,詳細可以看賦值運算符重載函數(shù),這樣是非常低效的,尤其是當(dāng)Line的數(shù)據(jù)成員非常龐大的時候,實際上我們只需要交換各自成員的指針就可以了!
三、swap的改進方案
? ? ? ? 我們希望告訴std::swap:當(dāng)Line被置換時,真正該做的是置換騎內(nèi)部的px和py指針。實現(xiàn)這個過程有幾個方案,我們先看最簡單的方案:
方案一:將std::swap針對Line特化
?
? ? ? ? C++規(guī)定:通常不允許改變std命名空間內(nèi)的任何東西,但是可以為標(biāo)準(zhǔn)template(如swap)制造特化版本,使他專屬于我們自己的class(例如Line)。
? ? ? ? 根據(jù)這個性質(zhì),我們可以對std:swap針對Line進行特化。我們可以這樣特化swap:
namespace std{ template<> void swap<Line>(Line& l1, Line& l2){ // std::swap()的特化版本,std::swap()只可以特化,不可以重載 cout<<"swap of std is called......"<<endl; l1.swap(l2); } }? ? ? ? 在這個代碼中,“template<>”表示它是std::swap的一個全特化版本,函數(shù)名之后的“<Line>"表示這一特化版本針對”T是Line”而設(shè)計。
?
? ? ? ? 完整的方案如下:
?
// SwapTest.cpp : 定義控制臺應(yīng)用程序的入口點。 #include "stdafx.h" #include <iostream> #include <vector> using namespace std; class Point{ private: int x,y; public: Point():x(0),y(0){}; Point(int a, int b):x(a),y(b){}; void Print(){ cout<< x << " "<< y <<endl; } int GetX(){ return x; } int GetY(){ return y; } }; class Line{ private: Point *px, *py; public: Line():px(),py(){}; Line(int a,int b,int c,int d):px(new Point(a,b)), py(new Point(c, d)){}; void swap(Line& l){ //Line成員函數(shù),用以實現(xiàn)指針成員交換 cout<<"swap of Line is called......"<<endl; using std::swap; swap(px, l.px); // 交換指針 swap(py, l.py); } void Print(){ cout<<"( "<<px->GetX()<<" "<<px->GetY()<<" "<<py->GetX()<<" "<<py->GetY()<<" )"<<endl; } }; namespace std{ template<> void swap<Line>(Line& l1, Line& l2){ // std::swap()的特化版本,std::swap()只可以特化,不可以重載 cout<<"swap of std is called......"<<endl; l1.swap(l2); } } int main(int argc, _TCHAR* argv[]) { Line l1(1,1,2,2), l2(3,3,4,4); swap(l1, l2); l1.Print(); return 0; }? ? ? ? 在這個例子中,一共出現(xiàn)了5次swap這個函數(shù):
?
? ? ? ? 第一次是main中調(diào)用的swap,這個調(diào)用的是我們自定義的std::swap()的特化版本
? ? ? ? 第二次是我們自己定義的std::swap()對Line類型的特化,在函數(shù)名前面有“template<>”
? ? ? ? 第三次是對Line特化的swap中調(diào)用的swap,也就是l1.swap(l2),這個很明顯是調(diào)用Line類型的swap()成員函數(shù)
? ? ? ? 第四次是Line類型中的成員函數(shù),void swap(Line& l),這個public函數(shù)的目的是供給非Line成員函數(shù)調(diào)用的,也就是特化版本的swap,因為只有類的成員函數(shù)才可以調(diào)用類的private成員變量
? ? ? ? 第五次是Line成員函數(shù)swap調(diào)用的swap,這個swap調(diào)用前面有個using std::swap的聲明,表示后面調(diào)用的是std中的原始swap,當(dāng)然不是特化版本的swap
? ? ? ? 其中可以被調(diào)用的swap有3個,std中原始的swap、std::swap的特化版本、Line中的成員函數(shù)swap,這3個函數(shù)中,真正給用戶調(diào)用的只有第一個swap,也就是std::swap的Line特化版。通過這一系列函數(shù)就可以實現(xiàn)Line對象中指針成員的指針的交換,而不是Line對象整個的交換。
? ? ? ? 這種方式和STL容器有一致性,因為所有的STL容器也都提供有public swap成員函數(shù)和std::swap特化版本(用以調(diào)用前者)
? ? ? ??
方案二、重載特化的std::swap
?
? ? ? ? 上面這種方式是針對Line和Point都是非template class,現(xiàn)在假設(shè)Line和Point都是template class,那么這種方式還可不可以了?
? ? ? ? 假設(shè)Line類和Point類都是template class,如下定義:
#include <iostream> #include <string.h> template<typename T> class Point{ private: T x,y; public: Point():x(0),y(0){}; Point(T a, T b):x(a),y(b){}; void Print(){ std::cout<< x << " "<< y <<endl; } T GetX(){ return x; } T GetY(){ return y; } }; template<typename T1, typename T2> class Line{ private: T1 px,py; public: Line():px(),py(){}; Line(T2 a,T2 b,T2 c,T2 d):px(T1(a, b)), py(T1(c, d)){}; void Print(){ std::cout<<"( "<<px.GetX()<<" "<<px.GetY()<<" "<<py.GetX()<<" "<<py.GetY()<<" )"<<std::endl; } void swap(Line<T1, T2>& l){ std::cout<<"swap of Line is called......"<<std::endl; using std::swap; swap(px, l.px); swap(py, l.py); } }; namespace std { template<typename t1="" typename="" t2=""> void swap(Line<t1 t2="">& l1, Line<t1 t2="">& l2){ cout<<"swap of std is called......"<<std::endl; l1.swap(l2); } } int main() { Line<Point<double>, double> l1(1, 1, 2, 2), l2(3, 3, 4, 4); std::swap(l1, l2); l1.Print(); return 0; }?
? ? ? ? 其中std里面的swap函數(shù)就是對std::swap的一個重載版本,然而,這個方式并不是特別的推薦,按照《effective c++》中的說法,這是一種非法的方式,是被C++標(biāo)準(zhǔn)禁止的,雖然能夠編譯和運行通過。
方案三、非特化非重載的non-member swap
? ? ? ? 我們可以聲明一個非Line類成員函數(shù)swap,讓其調(diào)用Line的成員函數(shù)swap,這個非成員swap也非特化的std::swap,如下所示:
?
? ? ? ? 其實就是將方案二的std::swap重載改成了自定義的非成員函數(shù),原理依然一樣!
remember:
? ? ? ? 1.當(dāng)std::swap對你的類型效率不高時,提供一個swap成員函數(shù),并確定這個函數(shù)不拋出異常。
? ? ? ? 2.如果你提供一個member swap,也該提供一個non-member swap用來調(diào)用前者。對于class(而非template),也請?zhí)鼗痵td::swap。
? ? ? ? 3.調(diào)用swap時應(yīng)針對std::swap使用using聲明式,然后調(diào)用swap并且不帶任何“命名空間資格修飾符”。
? ? ? ? 4.為“用戶定義類型”進行std template全特化是好的,但千萬不要嘗試在std內(nèi)加入某些對std而言全新的東西。
轉(zhuǎn)載于:https://www.cnblogs.com/wuchanming/p/3735189.html
總結(jié)
以上是生活随笔為你收集整理的《Effective C++》item25:考虑写出一个不抛异常的swap函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sublime Text 2 代码片断
- 下一篇: css翻页样式