C++程式碼複用的方法
情景:
對不同的物件,要執行相同的邏輯操作。在C++中有哪些方法實現?
解決方法:
1,模板,將不同的物件的型別作為模板引數。
//例:
int iarray[] = {2,6,4,8,3};
std::sort(iarray,iarray+sizeof(iarray)/sizeof(iarray[0]));
double farray[] = {2.0,6.0,4.0,8.0,3.0};
std::sort(farray,farray+sizeof(farray)/sizeof(farray[0]));
2,使用巨集,將不同型別的的物件作為巨集引數。
//例: #define max1(x,y) ((x) > (y) ? (x) : (y)) #define max2(x,y) ({typeof(x) _x = x; typeof(y) _y = y; _x > _y ? _x : _y;}) int maxint = max1(12,31); int l = 11, r = 30; int maxint2 = max2(++l,++r); double maxdouble = max1(12.4,42.6);
3,對不同的物件做一次抽象封裝,提取公共的基類,在基類中抽象虛擬函式。
class Base { public: virtual void Operate() const = 0; }; class Child1 : public Base { public: virtual void Operate() const {} }; class Child2 : public Base { public: virtual void Operate() const {} }; void Function(const Base* pObj) { if(pObj) { pObj->Operate(); } }
注意:
以上三種方法其實都可以歸納為C++的多型。前兩種屬於靜態多型,第三種屬於動態多型。所以都需要對操作的不同物件提供具有相同簽名的可供呼叫的方法或者屬性。
優缺點對比:
1,模板
優點:
1)效能好,模板引數都是在編譯時期已經特化並生成了制定型別的程式碼,而不是每次執行都需要動態處理,相對於動態多型來說少了執行時的型別檢查和匹配。
3)對一個有整形資料成員的類,可以有兩種定義方法:
和template <int T_1, int T_2> class class_template { public: class_template() : m_1(T_1) , m_2(T_2) {} private: int m_1; int m_2; };
class class_normal
{
public:
class_normal(int p_1, int p_2)
: m_1(p_1)
, m_2(p_2)
{}
private:
int m_1;
int m_2;
};
其實我們一直用的是第二種,而不會去使用第一種定義方法,第一種方法和第二種方法相比一個優勢是,建構函式少了引數傳遞,從編譯的彙編程式碼可以看出構造第一個類物件所需的操作要比第二個少,所以理論上來說效能要好於第二種類定義方法。缺點:
1)相對於使用巨集來說,模板只有兩種使用模式,模板類和模板函式,所以一些程式碼用巨集來寫方便但是用模板來說卻很複雜,需要額外處理很多的東西,比如如果要提取成函式則需要考慮到函式引數,並且直接提取成函式需要考慮的一個因素是return的含義變了,那麼程式碼可能還需要涉及到重構。
2)針對上述優點第三條所述,其實除了極少的優點(還包括例如參考連結中可以用於編譯期檢查的Int2Type)以外,缺點更多,首先,當每次使用class_templat<num1,num2> 來宣告一個物件的時候,只要num1或者num2變了,就相當於重新申明瞭一種新的型別,那麼class_template的類定義程式碼就需要再重複編譯進程式碼中一次,這樣就增加了最後生成的程式碼的佔用空間。並且,對num1和num2的取值,型別都有特殊的要求,擴充套件性很窄。並且上面所說的執行時效能的提高只是理論說法,實際測試中對程式碼執行效率並不會有太大的影響。
3)這個缺點主要體現在使用一些通過模版實現的包裝類,如果被包裝的類物件是一系列的具有多型關係的類,那麼需要進行一些特殊處理才能使包裝類也具有多型的性質(可以參考stl中的各種智慧指標,例如兩個類A和B,B繼承自A,B和A具有多型的特性,std::shared_ptr<A>和std::shared_ptr<B>之間依然存在多型性)。現實中要實現起來其實並不簡單,比如參考這麼一種場景:
因為設計需要,你的資料結構應該定義成“std::shared_ptr<YourPolymorphicType>”, 可以看到“YourPolymorphicType”並不會單獨使用,而是放到智慧指標裡面的,所以為了從技術手段上限制其能夠被以正確的方式使用,一個優先想到的解決方案是私有化這一系列類的建構函式,並且提供一個只能在堆上申請空間(new)的公有函式。解決方案看似不錯,但是由於“YourPolymorphicType”是一系列具有多型關係的類族,單純的將基類建構函式改成private則子類構造無法呼叫基類建構函式導致編譯錯誤,如果將基類建構函式改成public或者protected則由於提供了多型性,當業務擴充套件的時候“YourPolymorphicType”會膨脹,沒有一個強制的機制保證新的子類一定按最初設計的樣子來編寫(實現成只能new的形式),因為單純的依靠程式碼規範或者文件是沒有辦法做到嚴格約束不同coder行為的,畢竟不同的人水平不同,想法也不同。所以,新的方案是編寫一個模板類“template<typename T> class TypeOnlyNew; ”(類似的資料結構定義方式早有先例,比如一種模板方式的單例類實現),對“YourPolymorphicType”實現放開限制,但是將最初的“std::shared_ptr<YourPolymorphicType>”改成“std::shared_ptr<TypeOnlyNew<YourPolymorphicType>>”,那麼新的結構想要實現保證多型性就變得沒那麼簡單了。
2,巨集
優點:
1)簡單明瞭(粗暴),只是程式碼替換,不像函式(其他兩種方法都需要用到函式呼叫)那樣在執行時需要增加額外的開銷,同時也省去的封裝函式需要考慮的依賴資料要用引數傳遞的方式,那麼基於程式碼的可讀性和優美度需要進行的一些重構工作。
3)巨集的擴充套件程式碼,在不使用時是不會編譯進程式碼中的,所以在某些情況下,相比(比如程式碼重構導致的)未呼叫的函式來說,不需要擔心其會在生成的程式碼中造成冗餘。但是實際編碼最好不要出現沒有呼叫的函式或者沒有使用到的巨集殘存在程式碼中。
缺點:
1)必須要對巨集的用法特別熟悉(清楚其原理(程式碼替換,編譯期完成,不能遞迴)),才能使用得得心應手,而往往很多巨集引起的錯誤就是因為對原理理解不清或者在複雜環境下並沒能深刻理解其本質,比如講巨集和列舉搭配使用,將巨集和一元運算子搭配使用,以及在巢狀巨集呼叫中出現的擴充套件錯誤等,都是極其隱晦並且不容易理解的,所以使用需要慎重。 2)難於除錯,這時候只能保佑一大段的巨集程式碼中沒有出現bug,或者真的不幸撞上了,那就只能手動擴充套件巨集展開到使用的地方,然後單步除錯,但是如果巨集規模很大或者巢狀層次很多,那就更不幸了。。
3,繼承
優點:
1)面向物件程式語言的核心,優點不必多說了。
缺點:
1)C++繼承多型是通過虛擬函式表來實現的,所以和模板,巨集相比執行時效能會差。