您好,登錄后才能下訂單哦!
深入淺析C++ 的模板編程?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
類型模板
類型模板包括函數模板和類模板,基本上是C++開發人員接觸模板編程的起點。
下面代碼演示了函數模板和類模板的使用方法:
// 函數模板 template<typename T> T add(const T& a, const T& b) { return a + b; } // 類模板 template<typename T> class Point { private: T x[3]; ... };
類型模板以template
開始聲明,尖括號內的typename
關鍵字可用class
替代。類型模板中typename
和class
具有相同含義,均表示參數類型。實踐中typename
語義更廣泛,表示其后續的參數T
是一個類型,不限定于類,建議使用。類型參數T
可換成其他任意有意義的合法變量。
C++14新增變量模板:
// 變量模板 template<tyepename T> constexpr T pi = T(3.1415926535897932385L);
尖括號之于模板猶如小括號之于函數:函數通過小括號()定義和調用,模板使用尖括號<>定義(需template關鍵字聲明)和實例化。上面演示了類型模板定義,下面代碼介紹模板實例化:
int a = 1, b = 2; // 實例化函數模板 std::cout << "add result:" << add<int>(a, b) << std::endl; // 實例化類模板 auto p = Point<int>(); double radius = .5; // 實例化變量模板 auto area = pi<double> * radius * radius;
同函數一樣,模板可以有默認值:
// 默認類型為int template<typename T=int> T add(const T& a, const T& b) { return a + b; } // 默認類型為double template<typename T=double> class Point { private: T x[3]; ... };
與函數不同,對于函數模板,如果能從參數推斷出模板類型,則可略去尖括號模板實例化參數:
int a = 1, b = 2; // 合法調用,編譯器能根據a b推斷出參數類型 std::cout << "add result:" << add(a, b) << std::endl; // 等同于 std::cout << "add result:" << add<int>(a, b) << std::endl;
然而對于類模板,即使有默認參數,也不能省略尖括號(但是可以省去參數):
template<typename T=double> struct Point { T x[3]; }; // 合法聲明 auto p = Point<double>(); // 合法聲明,類型使用默認的double auto p2 = Point<>(); // 非法聲明,缺少模板調用標志尖括號 auto p3 = Point();
類型參數模板在實際中使用最多,STL庫中vector、map等容器、algorithm中的許多算法都用到了模板。
非類型參數模板
另一類常用模板是非參數模板,用來替代某個具體的值。例如:
// N維空間向量 template<int N> struct Vector { double x[N]; }; // 實例化 auto v = Vector<100>(); ...其他操作
需要注意的是,非類型參數模板能使用的類型十分有限,只有(signed/unsigned)整數、char和枚舉這幾種類型可用(參考switch
語法)。
同類型模板一樣,非類型參數模板也可以有默認值,但應用到類模板實例化也不能省略尖括號。
類型模板和非類型參數模板可以結合一起用:
template<typename T, int N> struct Point { T x[N]; };
類型模板解決了類型問題,非類型參數模板解決了值的問題,實際中應用也十分廣泛。作為遞歸的經典場景,斐波那契數列可以用非類型模板解決:
template<int N> struct Fib { static constexpr int value = Fib<N-1>::value + Fib<N-2>::value; }; // 模板特化 template<> struct Fib<1> { static constexpr int value = 1; }; // 模板特化 template<> struct Fib<0> { static constexpr int value = 0; }; // 調用 std::cout << "Fib(10): " << Fib<10>::value << std::endl;
這個例子出現了”模板特化”,接下來介紹。
模板特化/偏特化
定義模板后,希望在特定條件下使用單獨的模板,這便是模板特化。上文中斐波那契數列定義的template<int N> struct Fib是母模板,接下來又定義了0和1兩個特化模板(子模板),指示編譯器遇到Fib<0>和Fib<1>的情況,使用這兩組單獨定義。需要注意的是特化模板的template參數為空,具體模板參數放到了模板名稱處,類似于模板實例化。
對多個模板參數的情形,如果只特化某個模板參數,便是偏特化。例如:
// 泛型模板定義 template<typename T1, typename T2> struct Add; // 特化模板 template<> struct Add<int, int> {...}; // 偏特化模板 template<typename T> struct Add<T, long> {....};
模板特化/偏特化類似于函數重載,能針對特殊情況進行特別處理。
模板匹配與SFINAE
模板特化使得同一個模板名稱有了多個定義,代碼具體調用時會遇到模板匹配問題。理解模板匹配機制的關鍵便是SFINAE,這也是進階模板編程的必備知識點。
SFINAE是Substitution failure is not an error的縮寫,翻譯過來便是:匹配(替換)失敗不是錯誤。
怎么理解這句話呢?
對于上面的斐波那契數列數列代碼,編譯器遇到Fib<10>::value的代碼,(可能)先會嘗試匹配Fib<0>,發現匹配不上,這是一個Substitution failure,但不是error,所以編譯器繼續嘗試其他可能性。接著匹配Fib<1>,同樣發現匹配不上,忽略這個Substitution failure繼續嘗試Fib<N>,OK,這一次沒問題,編譯成功。
如果是Fib<-1>::value,編譯器達到最大遞歸深度也找不到一個合適的匹配模板,這是一個error,因此編譯失敗。
備注:理解上面的話需要對編譯過程稍加了解,編譯過程會輸出許多信息,編譯器一般只有遇到error才會終止編譯,比較常見的warning則不會。模板匹配中的Substitution可能連warning都算不上,不會影響編譯器繼續嘗試匹配
理解SFINAE是看懂稍微深奧點模板代碼的基本功,重點便是:不怕你模板多,就怕找不到合適的模板。
兩階段編譯
有了模板(元)編程,C++源碼編譯可以分為前期和后期,構成兩階段編譯。前期是模板的天下,編譯器掃描模板實例化語句,生成運算結果和具體代碼;后期編譯器介入,再編譯生成機器碼。
模板代碼運行在編譯期,因此有如下特點:
template<int N> struct Point {double x[N];}; // 根據輸入動態生成類,無法實現和編譯成功 int n; std::cin >> n; auto p = new Point<n>();
C/C++編譯有個預處理過程,只是做簡單字符串替換,沒有具體運算,與模板生成代碼不同
在編譯前期,除了模板代碼被解釋執行,其他代碼信息都在,因此模板代碼擁有類似反射/自省的能力,這也是C++元編程功能強大的原因之一。
C++11中的變化
C++11帶來了許多新特性和重大更新,可以認為C++11是一門新的語言。就模板來說,主要更新點如下:
1. 可以使用static constexpr int代替早期模板代碼中的enum。網上許多斐波那契數列代碼都是基于早期C++,一律使用enum方式定義字段;
2. 可以使用using
代替typedef
。這是using語句能力的重大更新,早期我們定義類型或者別名都需要typedef
,自C++11開始,簡單使用using
就可以達到相同效果。
3. C++14引入了變量模板,上文已介紹。
模板優缺點
上文根據自己理解和實踐簡要介紹了C++模板編程的相關概念,本節總結一下C++模板的優缺點:
C++模板編程優點:
關于深入淺析C++ 的模板編程問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。