C++Template 模版的本质
?
?
我想知道上帝的構思,其他的都祇是細節。? ? ??? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ——愛因斯坦
?
Alex 碼農的藝術,騰訊資深工程師,騰訊云網絡核心成員,獲得過多次五星員工(鵝廠優秀員工),面試經驗豐富,熱愛技術與運動,微信搜一搜【極客重生】關注我,期待與大家一起學習交流,認真回答每一個問題;
?
?
?
?
前言
?
C++的一些高級特性對于新人來說,很具有挑戰性,而模板就是其中之一,晦澀語法讓很多新人望而生畏;大多數人苦苦磨煉,卻始終沒有掌握這門絕學,本文通過揭開模板的一些面紗,希望幫助新人掌握模板的心法,從而學會這門武功(技術),助你跨過C++這座大山,向C++頂級程序員邁進,升職加薪;
?
Content
C++模版的誕生
C++模板的實現
C++類模板(class template)技術
C++函數模板(function template)技術
C++模板的核心技術
C++模版應用場景
C++模版的展望
?
C++模版的誕生
程序 = 數據結構 + 算法?? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ??? ? ? ? ? ? ? ? ?---Niklaus EmilWirth
?
程序本質是數據結構+算法,任何一門語言都可以這樣理解,這個公式對計算機科學的影響程度足以類似物理學中愛因斯坦的“E=MC^2”——一個公式展示出了程序的本質。
?
最初C++是沒有標準庫的,任何一門語言的發展都需要標準庫的支持,為了讓C++更強大,更方便使用,Bjarne Stroustrup覺得需要給C++提供一個標準庫,但標準庫設計需要一套統一機制來定義各種通用的容器(數據結構)和算法,并且能很好在一起配合,這就需要它們既要相對的獨立,又要操作接口保持統一,而且能夠很容易被別人使用(用到實際類中),同時又要保證開銷盡量小(性能要好)。Bjarne Stroustrup 提議C++需要一種機制來解決這個問題,所以就催生了模板的產生,最后經標準委員會各路專家討論和發展,就發展成如今的模版, C++ 第一個正式的標準也加入了模板。
C++模版是一種解決方案,初心是提供參數化容器類和通用的算法(函數),目的就是為了減少重復代碼,讓通用性和高性能并存,提高C++程序員生產力。
?
?
什么是參數化容器類?
?
首先C++是可以提供OOP(面向對象)范式編程的語言,所以支持類概念,類本身就是現實中一類事物的抽象,包括狀態和對應的操作,打個比喻,大多數情況下我們談論汽車,并不是指具體某輛汽車,而是某一類汽車(某個品牌),或者某一類車型的汽車。
?
所以我們設計汽車這個類的時候,各個汽車品牌的汽車大體框架(骨架)都差不多,都是4個輪子一個方向盤,而且操作基本上都是相同的,否則學車都要根據不同廠商汽車進行學習,所以我們可以用一個類來描述汽車的行為:
class Car{ public: Car(...);//other operations...private:Tire?m_tire[4]; Wheel?m_wheel;//other attributes... };?
但這樣設計擴展性不是很好,因為不同的品牌的車,可能方向盤形狀不一樣,輪胎外觀不一樣等等。所以要描述這些不同我們可能就會根據不同品牌去設計不同的類,這樣類就會變得很多,就會產生下面的問題:
1. 代碼冗余,會產生視覺復雜性,本身相似的東西比較多;
2. 用戶很難通過配置去實現一輛車設計,不好定制化一個汽車;
3. 如果有其中一個屬性有新的變化,就得實現一個新類,擴展代價太大。
?
這個時候,就希望這個類是可以參數化的(屬性參數化),可以根據不同類型的參數進行屬性配置,繼而生成不同的類。類模板就應運而生了,類模板就是用來實現參數化的容器類。
?
什么是通用算法?
?
程序=數據結構+算法
?
算法就是對容器的操作,對數據結構的操作,一般算法設計原則要滿足KISS原則,功能盡量單一,盡量通用,才能更好和不同容器配合,有些算法屬于控制類算法(比如遍歷),還需要和其他算法進行配合,所以需要解決函數參數通用性問題。舉個例子, 以前我們實現通用的排序函數可能是這樣:
?
void sort (void* first, void* last, Cmp cmp); ?這樣的設計有下面一些問題:
1. 為了支持多種類型,需要采用void*參數,但是void*參數是一種類型不安全參數,在運行時候需要通過類型轉換來訪問數據。
2. 因為編譯器不知道數據類型,那些對void*指針進行偏移操作(算術操作)會非常危險(GNU支持),所以操作會特別小心,這個給實現增加了復雜度。
?
所以要滿足通用(支持各種容器),設計復雜度低,效率高,類型安全的算法,模板函數就應運而生了,模板函數就是用來實現通用算法并滿足上面要求。
?
C++模板的實現
?
C++標準委員會采用一套類似函數式語言的語法來設計C++模板,而且設計成圖靈完備 (Turing-complete)(詳見參考),我們可以把C++模板看成是一種新的語言,而且可以看成是函數式編程語言,只是設計依附在(借助于)C++其他基礎語法上(類和函數)。
C++實現類模板(class template)技術
?
1.定義模板類,讓每個模板類擁有模板簽名。
-
?
上面的模板簽名可以理解成:X<typename T>; 主要包括模板參數<typename T>和模板名字X(類名), 基本的語法可以參考《C++ Templates: The Complete Guide》,《C++ primer》等書籍。
模板參數在形式上主要包括四類,為什么會存在這些分類,主要是滿足不同類對參數化的需求:
-
type template parameter:?類型模板參數,以class或typename 標記;此類主要是解決樸實的參數化類的問題(上面描述的問題),也是模板設計的初衷。
-
non-type template parameter:?非類型模板參數,比如整型,布爾,枚舉,指針,引用等;此類主要是提供給大小,長度等整型標量參數的控制,其次還提供參數算術運算能力,這些能力結合模板特化為模板提供了初始化值,條件判斷,遞歸循環等能力,這些能力促使模板擁有圖靈完備的計算能力。
-
template template parameter,模板參數是模板,此類參數需要依賴其他模板參數(作為自己的入參),然后生成新的模板參數,可以用于策略類的設計policy-base class。
-
parameter pack,C++11的變長模板參數,此類參數是C++11新增的,主要的目的是支持模板參數個數的動態變化,類似函數的變參,但有自己獨有語法用于定義和解析(unpack),模板變參主要用于支持參數個數變化的類和函數,比如std::bind,可以綁定不同函數和對應參數,惰性執行,模板變參結合std::tuple就可以實現。
?
2. 在用模板類聲明變量的地方,把模板實參(Arguments)(類型)帶入模板類,然后按照匹配規則進行匹配,選擇最佳匹配模板. 模板實參和形參類似于函數的形參和實參,模板實參只能是在編譯時期確定的類型或者常量,C++17支持模板類實參推導。
?
3. 選好模板類之后,編譯器會進行模板類實例化--記帶入實際參數的類型或者常量自動生成代碼,然后再進行通常的編譯。
?
C++實現模板函數(function template)技術
?
模板函數實現技術和模板類形式上差不多:
?
template<typename T>retType function_name(T t);其中幾個關鍵點:
-
函數模板的簽名包括模板參數,返回值,函數名,函數參數, cv-qualifier;
-
函數模板編譯順序大致:名稱查找(可能涉及參數依賴查找)->實參推導->模板實參替換(實例化,可能涉及 SFINAE)->函數重載決議->編譯;
-
函數模板可以在實例化時候進行參數推導,必須知道每個模板的實參,但不必指定每個模板的實參。編譯器會從函數實參推導缺失的模板實參。這發生在嘗試調用函數、取函數模板地址時,和某些其他語境中;
-
函數模板在進行實例化后會進行函數重載解析, 此時的函數簽名不包括返回值(template ?argument deduction/substitution);
-
函數模板實例化過程中,參數推導不匹配所有的模板或者同時存在多個模板實例滿足,或者函數重載決議有歧義等,實例化失敗;
-
為了編譯函數模板調用,編譯器必須在非模板重載、模板重載和模板重載的特化間決定一個無歧義最佳的模板;
?
C++模板的核心技術
?
1. SFINAE -Substitution failure is not an error?
要理解這句話的關鍵點是failure和error在模板實例化中意義,模板實例化時候,編譯器會用模板實參或者通過模板實參推導出參數類型帶入可能的模板集(模板備選集合)中一個一個匹配,找到最優匹配的模板定義,
Failure:在模板集中,單個匹配失敗;
Error:在模板集中,所有的匹配失敗;
所以單個匹配失敗,不能報錯誤,只有所有的匹配都失敗了才報錯誤。
2. 模板特化
模板特化為了支持模板類或者模板函數在特定的情況(指明模板的部分參數(偏特化)或者全部參數(完全特化))下特殊實現和優化,而這個機制給與模板某些高階功能提供了基礎,比如模板的遞歸(提供遞歸終止條件實現),模板條件判斷(提供true或者false 條件實現)等。
3. 模板實參推導
模板實參推導機制給與編譯器可以通過實參去反推模板的形參,然后對模板進行實例化,具體推導規則見參考;
4. 模板計算
模板參數支持兩大類計算:
一類是類型計算(通過不同的模板參數返回不同的類型),此類計算為構建類型系統提供了基礎,也是泛型編程的基礎;
一類是整型參數的算術運算, 此類計算提供了模板在實例化時候動態匹配模板的能力;實參通過計算后的結果作為新的實參去匹配特定模板(模板特化)。
5. 模板遞歸
模板遞歸是模板元編程的基礎,也是C++11變參模板的基礎。
?
C++模版的應用場景
?
1. C++ Library:
?
可以實現通用的容器(Containers)和算法(Algorithms),比如STL,Boost等,使用模板技術實現的迭代器(Iterators)和仿函數(Functors)可以很好讓容器和算法可以自由搭配和更好的配合;
?
2.??C++ type traits
?
通過模板技術,C++ type traits實現了一套操作類型特性的系統,C++是靜態類型語言,在編譯時候需要對變量和函數進行類型檢查,這個時候type traits可以提供更多類型信息給編譯器,?能讓程序做出更多策略選擇和特定類型的深度優化,Type Traits有助于編寫通用、可復用的代碼。
C++創始人對traits的理解:
"Think of a trait as a small object whose main purpose is to carry information used by another object or algorithm to determine "policy" or "implementation details". - Bjarne Stroustrup
?
而這個技術,在其他語言也有類似實現,比如go的interface,java的注解,反射機制等。
?
3. Template metaprogramming-TMP
?
?
隨著模板技術發展,模板元編程逐漸被人們發掘出來,metaprogramming本意是進行源代碼生成的編程(代碼生成器),同時也是對編程本身的一種更高級的抽象,好比我們元認知這些概念,就是對學習本身更高級的抽象。TMP通過模板實現一套“新的語言”(條件,遞歸,初始化,變量等),由于模板是圖靈完備,理論上可以實現任何可計算編程,把本來在運行期實現部分功能可以移到編譯期實現,節省運行時開銷,比如進行循環展開,量綱分析等。
?
4. Policy-Based Class Design
?
C++ Policy class design 首見于 Andrei Alexandrescu 出版的 《Modern C++ Design》一書以及他在C/C++ Users Journal雜志專欄 Generic<Programming>,參考wiki。通過把不同策略設計成獨立的類,然后通過模板參數對主類進行配置,通常policy-base class design采用繼承方式去實現,這要求每個策略在設計的時候要相互獨立正交。STL還結合CRTP (Curiously recurring template pattern)等模板技術,實現類似動態多態(虛函數)的靜態多態,減少運行開銷。
?
5. Generic Programming(泛型編程)
?
由于模板這種對類型強有力的抽象能力,能讓容器和算法更加通用,這一系列的編程手法,慢慢引申出一種新的編程范式:泛型編程。泛型編程是對類型的抽象接口進行編程,STL庫就是泛型編程經典范例。
?
C++模版的展望
?
1. 模版的代價
?
沒有任何事物是完美的,模板設計如此精良也有代價的,模板的代碼和通常的代碼比起來,
-
代碼可讀性差,理解門檻高
一般人初學者很難看懂,開發和調試比較麻煩,對人員要求高,是跨越C++三座大山之一;
-
代碼實現穩定性代價大
對模板代碼,實際上很難覆蓋所有的測試,為了保證代碼的健壯性,需要大量高質量的測試,各個平臺(編譯器)支持力度也不一樣(比如模板遞歸深度,模板特性等),可移植性不能完全保證。模板多個實例很有可能會隱式地增加二進制文件的大小等,所以模板在某些情況下有一定代價,一定要在擅長的地方發揮才能;
?
如何降低門檻,對初學者更友好,如何降低復雜性,這個是C++未來發展重要的方向。現代c++正在追求讓模板,或者說編譯期的計算和泛型約束變簡單,constexpr,concept,fold expression,還有C++ 20一大堆consteval,constinit,constexpr virtual function,constexpr dynamic cast,constexpr container等等特性的加入就是為了解決這些問題。曾經的遞歸變成了普通的constexpr函數,曾經的SFINAE變成了concept,曾經的枚舉常量變成了constexpr常量,曾經的遞歸展開變成了fold expression,越來越簡單,友好了。
?
2. 基于模板的設計模式
?
隨著C++模板技術的發展,以及大量實戰的經驗總結,逐漸形成了一些基于模板的經典設計,比如STL里面的特性(traits),策略(policy),標簽(tag)等技法;Boost.MPL庫(高級C++模板元編程框架)設計;Andrei Alexandrescu 提出的Policy-Based Class Design;以及Jim Coplien的curiously recurring template pattern (CRTP),以及衍生Mixin技法;或許未來,基于模板可以衍生更多的設計模式,而這些優秀的設計模式可以實現最大性能和零成本抽象,這個也是C++的核心精神。
?
詳細參考這本書(有電子書):
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms
?
3. 模板的未來
?
隨著模板衍生出來的泛型編程,模板元編程,模板函數式編程等理念的發展,將來也許會發展出更抽象,更通用編程理念。模板本身是圖靈完備的,所以可以結合C++其他特性,編譯期常量和常量表達式,編譯期計算,繼承,友元friend等開闊出更多優雅的設計,比如元容器,類型擦除,自省和反射(靜態反射和metaclass)等,將來會出現更多優秀的設計。
?
?
更多細節請參考資料和進一步閱讀:
1《The design and Evolution of C++ 》Bjarne Stroustrup;
2. C++ Templates are Turing Complete,Todd L. Veldhuizen,2003(作者網站已經停了,archive.org 保存的版本,archive.org 可能被限制瀏覽);
3. 模板細節:
wikipedia.org, cppreference.com(C++,模板template, Template metaprogramming, CRTP (Curiously recurring template pattern), Substitution failure is not an error (SFINAE), template_argument_deduction ,Policy-based_class design, Expression templates,等);
C++ Programming/Templates/Template Meta-Programming
4.模板的基本語法:
?C++標準ISO+IEC+14882-1998,2003,2011;
《C++ Templates: The Complete Guide》 by David Vandevoorde, Nicolai M. Josuttis;
5.模板設計進階:
Andrei Alexandrescu 的 《Modern C++ Design》;
候捷的《STL源碼剖析》;
More C++ Idioms:wikipedia.org
6. 模板元編程:
Abrahams, David; Gurtovoy, Aleksey. C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond. Addison-Wesley. ISBN 0-321-22725-5.
Metaprogramming in C++,Johannes Koskinen,2004
Boost 的MPL庫
7.Blog and papers:
Coplien, James O. (February 1995). "Curiously Recurring Template Patterns" (PDF). C++ Report: 24–27.
https://en.wikipedia.org/wiki/Mixin
http://www.cnblogs.com/liangliangh/p/4219879.html
http://b.atch.se/posts/non-constant-constant-expressions
?
希望本文對想學習C++的同學有一定的幫助,如果精通了C++,學Java,C#,go,Python,Rust等這類的語言將是非常輕松的!?想更多交流可以公眾號回復“入群”,祝大家職場重生,成為大拿
總結
以上是生活随笔為你收集整理的C++Template 模版的本质的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小白如何学习大神的小项目
- 下一篇: linux调度全景指南