條款23:寧以non-memeber、non-friend替換member函數
? ? ? ? ? non-member/non-friend可以給對象帶來更大的封裝性,從兩個方面來考慮:1)考慮封裝,越多東西被封裝,它們就越不可見,就越少人看到它,就會越有彈性去改變它;2)考慮對象內的數據,越少代碼可以看到數據,越多的數據被封裝,那么我們就越能自由地改變對象數據;
? ? ? ? ? 在C++中,可以讓所有的non-member函數放在同一個命名空間內,然后使用那個命名空間;C++標準程序庫并不是單一、整體、龐大的頭文件,而是很多頭文件組合而成的,這就允許客戶只對他們所有的那部分系統形成編譯相依;
條款24:若所有參數都需類型轉換,用non-member代替member函數
? ? ? ? ? ? ?對于參數都允許發生隱式轉換的函數,使用non-member函數可以使你的類功能更加具有一致性,而且還支持混合式算術編程;具體代碼分析如下:
? ? ? ? ?const rational operator*(const rational&rhs)const{
? ? ? ? ? ? ? ?return rational(this->numerator( )*rhs.numerator( ),this->denominator( )*rhs.denominator( );
}
rational onehalf(1,2);
rational result=2*onehalf;//錯誤,調用實際情況是2.operator(&2,onehalf);//不能對常量取地址操作
rational result=onehalf*2;//正確
修改為non-member函數
const rational operator*(const rational& lhs ,? const rationa & rhs){
? ? ? return rational(lhs.numerator( )*rhs.numerator( ),lhs.denominator( )*rhs.denominator( );
}
結論:如果你需要為某個函數的所有參數(this指針所指的那個隱喻參數)進行類型轉換,那么這個函數必須是個non-member;
條款25:考慮寫出一個不拋異常的swap函數
? ? ? ? ? ? ?swap原本是STL中的一部分,后成為異常安全性編程的脊柱以及用來處理自我賦值可能的一種常見機制;
?1.缺省情況下,std標準庫的swap算法:
? ? ? ? ? ? ? ? ? template<typename T>
? ? ? ? ? ? ? ? ? void swap(T&a,T&b){T temp(a);a=b;b=temp;}//實現是基于T的copy構造函數和copy assignment完成
2.通常情況下,數據的表現形式都是“以指針指向一個對象,內含真正的數據”,這種設計模式通常變現為pimpl(pointer to implementation)手法,如:
class widgetimpl{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? class widget{
private:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?private:
? ? ? ? ? int a ,b,c;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?widgetimpl*pimpl;
? ? ? ? ? std::vector<double>v;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };
}
如果需要交換兩個widget對象,那么我們唯一需要做的就是交換兩個pimpl指針,但是普通的swap算法卻是復制了三次widgetimpl;
解決上述問題的一個方法:就是將std::swap針對widget全特化(模板函數的一個實例),然后用widget的成員函數調用它(寫錯了)用全特化的swap函數來調用public swap成員函數,具體代碼如下:
class widget{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?namespace std{
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? template<>//針對widget的特化版本
? ? ? ? ? void swap(widget&other){? ? ? ? ? ? ? ? ? ? ? ? ? ?void swap<widget>(widget&a.widget&b){
? ? ? ? ? using std::swap;//讓std內的swap可用? ? ?? ? ? ? ? ? ? ? ? ? ? ?a.swap(b);? ? ? ? ? ? //調用swap成員函數;
? ? ? ? ?swap(pimpl,other.pimpl);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
};? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}
3.如果widget和widgetpimpl都是class templates而非class,那么我們重新定義非成員函數;
? ? ? ?template<typename T>
? ? ? void swap<widget<T>>(widget<T>&a,widget<T>&b){ a.swap(b);}? ? ? ?//不合法,錯誤
原因是我們企圖對function template偏特化,但是C++只允許對class template偏特化;
引申:std是一個特殊的命名空間,其管理規則也比較特殊,客戶可以全特化(實例化)std內的template,但是不可以添加新的templates(class或者templates或者其他的任何東西)到std里頭,C++禁止這類行為,但是編譯器卻不會報錯,但是軟件可能會出現不可預期的行為;
? ? 解決上述問題的一個辦法:添加一個重載函數來代替我們要做的偏特化一個function template行為,我們還是聲明一個non-member swap函數讓他調用member swap,但不再聲明將那個non-member swap聲明為std::swap的特化版本,具體實現代碼如下:
?template<typename T>? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???template<typename T>
class widget{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?void swap(T&obj1,T&obj2){
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?using std::swap;
? ? ? ? void swap(widget&other){? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?swap(obj1,obj2);//T型對象的最佳調用swap版本
? ? ? ??? using std::swap;? ? ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}? ?//優先調用T專屬版本,即public swap成員函數? ? ? ? ? ? ? ? ? ??
? ? ? ? ?swap(pimpl,other.pimpl);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//并在該版本不存在的情況下調用std內一般化的版本
}
結論:1)如果swap缺省實現碼可以實現你的class或者class template提供可接受的效率,你不需要做任何事情;
? ? ? ? ? 2)如果swap缺省碼不夠實現你的效率,你可以:
? ? ? ? ? ? ? ?a)提供一個public swap成員函數,讓它處理兩個對象值;
? ? ? ? ? ? ? ?b)在你所在的class或者template所在的命名空間內提供一個non-member swap,并令它調用上述swap成員函數;
? ? ? ? ? ? ? ?c)如果你在編寫一個class(而非class template),為你的class特化std::swap,并令它調用你的swap成員函數。
? ? ? ? 3)如果你調用swap,確定包含一個using聲明式,以便std::swap在你的函數內曝光,然后不加namespace,單純調用swap(使swap函數有更多的選擇);
成員版的swap絕不可能拋出異常,因為swap的一個最好應用就是幫助classes提供強烈的異常安全性保障(以copy構造函數和copy assignment操作符為基礎的);
引申:關于模板的全特化和偏特化描述,可以參考博文:https://www.cnblogs.com/yyehl/p/7253254.html