1. 程式人生 > >C++Template 模版的本質

C++Template 模版的本質

    C++ Template  模版的本質

     自動化是人類進化的動力

  AlexCool

                             本文出現的目的,就是儘量讓人們理解C++模版設計的思想, 屬於模板的心法。

我想知道上帝是如何創造這個世界的。我對這個或那個現象,這個或那個元素的能譜不感興趣。我要知道的是他的思想。其他都是細節。

——愛因斯坦

模版最初的目的就是為了減少重複程式碼,所以模版出現的目的就是為了解放C++程式設計師生產力。

 content

 C++模版是什麼

     什麼是引數化容器類

     什麼是通用演算法

模板是怎麼解決上面問題的

      C
++實現引數化類(class template)技術 C++實現模板函式(function template)技術 實現C++模板的幾個核心技術 模版典型的應用場景有哪些 對模版的展望 更多細節請參考資料和進一步閱讀

 C++模版是什麼?

程式 = 資料結構  + 演算法

                                              ---Niklaus EmilWirth

最初C++是沒有標準庫的,任何一門語言的發展都需要標準庫的支援,為了讓C++更強大,Bjarne Stroustrup覺得需要給C++提供一個標準庫,但標準庫設計需要一套統一機制來定義各種通用的容器(資料結構)和演算法,並且能很好在一起配合,這就需要它們既要相對的獨立,又要操作介面保持統一,而且能夠很容易被別人使用(用到實際類中),同時又要保證開銷儘量小(效能要好)。 Bjarne Stroustrup 提議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++模板,而且設計成圖靈完備 (Turing-complete)(詳見參考),我們可以把C++模板看成是一種新的語言,而且可以看成是函數語言程式設計語言,只是設計依附在(藉助於)C++其他基礎語法上(類和函式)。

 

 C++實現引數化類(class template)技術:

1.定義模板類,讓每個模板類擁有模板簽名。

模板類語法:

template<typename T>

class X{...};
  • 上面的模板簽名可以理解成: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);

其中幾個關鍵點:

  1. 函式模板的簽名包括模板引數,返回值,函式名,函式引數,  cv-qualifier;
  2. 函式模板編譯順序大致:名稱查詢(可能涉及引數依賴查詢)->實參推導->模板實參替換(例項化,可能涉及 SFINAE)->函式過載決議->編譯;
  3. 函式模板可以在例項化時候進行引數推導,必須知道每個模板的實參,但不必指定每個模板的實參。編譯器會從函式實參推導缺失的模板實參。這發生在嘗試呼叫函式、取函式模板地址時,和某些其他語境中;
  4. 函式模板在進行例項化後(template argument deduction/substitution)會進行函式過載解析(overloading resolution, 此時的函式簽名不包括返回值;
  5. 函式模板例項化過程中,引數推導不匹配所有的模板或者同時存在多個模板例項滿足,或者函式過載決議有歧義等,例項化失敗;
  6. 為了編譯函式模板呼叫,編譯器必須在非模板過載、模板過載和模板過載的特化間決定一個無歧義最佳的模板;

 

實現C++模板的幾個核心技術:

  1. SFINAE -Substitution failure is not an error ;

      要理解這句話的關鍵點是failure和error在模板例項化中意義,模板例項化時候,編譯器會用模板實參或者通過模板實參推匯出引數型別帶入可能的模板集(模板備選集合)中一個一個匹配,找到最優匹配的模板定義,

        Failure:在模板集中,單個匹配失敗;

        Error: 在模板集中,所有的匹配失敗;

所以單個匹配失敗,不能報錯誤,只有所有的匹配都失敗了才報錯誤。

        2.  模板特化

      模板特化為了支援模板類或者模板函式在特定的情況(指明模板的部分引數(偏特化)或者全部引數(完全特化))下特殊實現和優化,而這個機制給與模板某些高階功能提供了基礎,比如模板的遞迴(提供遞迴終止條件實現),模板條件判斷(提供true或者false 條件實現)等。

       3. 模板實參推導

模板實參推導機制給與編譯器可以通過實參去反推模板的形參,然後對模板進行例項化,具體推導規則見參考;

       4. 模板計算

模板引數支援兩大類計算:

    • 一類是型別計算(通過不同的模板引數返回不同的型別),此類計算為構建型別系統提供了基礎,也是泛型程式設計的基礎;
    • 一類是整型引數的算術運算, 此類計算提供了模板在例項化時候動態匹配模板的能力;實參通過計算後的結果作為新的實參去匹配特定模板(模板特化)。

       5. 模板遞迴

 模板遞迴是模板超程式設計的基礎,也是C++11變參模板的基礎。

模版典型的應用場景有哪些?

  1. C++ Library:

 可以實現通用的容器(Containers)和演算法(Algorithms),比如STL,Boost等,使用模板技術實現的迭代器(Iterators)和仿函式(Functors)可以很好讓容器和演算法可以自由搭配和更好的配合;

        2  C++ type traits

通過模板技術,C++  type traits實現了一套操作型別特性的系統,C++是靜態型別語言,所以需要再編譯時候對型別進行檢查,這個時候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

        3. Template metaprogramming-TMP

隨著模板技術的發展,模板超程式設計逐漸被人們發掘出來,metaprogramming本意是進行原始碼生成的程式設計(程式碼生成器),同時也是對程式設計本身的一種更高階的抽象,好比我們元認知這些概念,就是對學習本身更高階的抽象。 TMP通過模板實現一套“新的語言”(條件,遞迴,初始化,變數等),由於模板是圖靈完備,理論上可以實現任何可計算程式設計,把本來在執行期實現部分功能可以移到編譯期實現,節省執行時開銷,比如進行迴圈展開,量綱分析等。

                                                           (取自參考文獻6)

        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庫就是泛型程式設計經典範例。     

對模版的展望

  1. 模版帶來的缺點是什麼?

     模板的程式碼和通常的程式碼比起來,可讀性很差,所以很難維護,對人員要求非常高,開發和除錯比較麻煩。 對模板程式碼,實際上很難覆蓋所有的測試,所以為了保證程式碼的健壯性,需要大量高質量的測試,各個平臺(編譯器)支援力度不一樣(模板遞迴深度,模板特性),可移植性不能完全保證。模板多個例項很有可能會隱式地增加二進位制檔案的大小等,所以模板在某些情況下有一定代價;

        2. 基於模板的設計模式

      隨著C++模板技術的發展,以及大量的經驗總結,逐漸形成了一些基於模板的經典設計,比如STL裡面的特性(traits),策略(policy),標籤(tag)等技法;Andrei Alexandrescu 提出的Policy-Based Class Design;以及Jim Coplien的curiously recurring template pattern (CRTP),以及衍生Mixin技法;或許未來,基於模板可以衍生更多的設計模式。

        3.  模板的未來

C++標準準備引進Concepts(Concepts: The Future of Generic Programming by Bjarne Stroustrup)解決模板除錯問題,對模板介面進行約束;隨著模板衍生出來的泛型程式設計,模板超程式設計,模板函數語言程式設計等理念的發展,將來也許會發展出更抽象,更通用程式設計理念。模板本身是圖靈完備的,所以可以結合C++其他特性:編譯期常量和常量表達式,編譯期計算,繼承,友元friend等開闊出更多優雅的設計,比如元容器,型別擦除,自省和反射等,將來會出現更多設計。

希望本文對想學習C++模板的同學有一定的幫助,有不對的地方還請指正,多謝!

更多細節請參考資料和進一步閱讀:

        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,等);

       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.

7.Blog and papers:

相關推薦

C++Template 模版本質

    C++ Template  模版的本質     自動化是人類進化的動力  AlexCool                             本文出現的目的,就是儘量讓人們理解C++模版設計的思想, 屬於模板的心法。我想知道上帝是如何創造這個世界的。我對這個或那

C++ template模版長期舉例總結

example_1 #include<iostream> using namespace std; #include<stdio.h> template<typename T> class ID { public: ID() : a(0)

從 art-template 模版維護到動態加載的思考

高亮 log fun 統一 logs don 重新 自己 編譯   自己用 art-template 有些年頭了,最近在培養團隊學習 art-template 使用,發現有一個痛點比較難解決。   比如有一個模版,我們可以直接寫在頁面中,像這樣: <scrip

C++學習筆記----2.4 C++引用在本質上是什麽,它和指針到底有什麽區別

times 數據 添加 eof 相關 參數 副本 span 符號 從概念上講。指針從本質上講就是存放變量地址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向的地址的改變和其指向的地址中所存放的數據的改變。 而引用是一個別名,它在邏輯上不是獨立的,它的存在具有依附性,

template 模版制作

script scale imu mobile set use OS format tex <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8">

c++template模板類問題

菜市場 感情 emp 守護 pla 只知道 呵呵 是什麽 放下 最喜歡藍色,是天空的藍色,純凈無瑕、晶瑩美好。 不知道是什麽時候開始把夢想看得這樣重要,只知道,夢想,真的、真的、真的、重要。 每個人都有值得自己守護的夢,或許很清!晰或許還沒有一個準確的定位,或許,你根本不相

Objective-C 類的本質

Objective-C (以下簡稱 OC )是一門動態性強的程式語言,OC 的動態性是基於 Runtime 來實現的,Runtime 系統是由 C\C++\組合語言 編寫的,提供的 API 基本都是 C 語言的。這裡我們從蘋果提供的 Runtime 程式碼來探究類的本質。 runtime 原始碼地址 le

C++標準模版庫STL

思維導圖 STL共有六大元件: 一。容器(Container):是一種資料結構,如list,vector,deque,queue等,以模板類的方法提供,為了訪問容器中的資料,可以使用由容器類提供的迭代器。 二。迭代器(Iterator):提供了訪問容器中物件的方法。 三。演算法(Al

[C++ Template]基礎--類模板

3 類模板 與函式相似, 類也可以被一種或多種型別引數化。   3.1 類模板Stack的實現 template <typename T> class Stack { private: std::vector<T> elems; // 儲存元素的容

[C++ Template]基礎--函式模板

2 函式模板 函式模板是那些被引數化的函式, 它們代表的是一個函式家族。   2.1 初探函式模板 如下就是一個返回兩個值中最大值的函式模板: template <typename T> inline T const& max(T const&

template 模版引擎 ——輪播圖

html原始碼: <div id="scrollPic" class="carousel slide" data-scrollPic> <!-- 輪播(Carousel)指標 --> <ol class="carousel-indica

C++ template除錯方法

template函式/類一定要具化後才能除錯。 如下程式碼: #include <iostream> using namespace std; template <typename T> T add(T a, T b) { return (a + b); }

django的Template模版知識

  一.使用模板的原因 對頁面設計進行的任何改變都必須對 Python 程式碼進行相應的修改。 站點設計的修改往往比底層 Python 程式碼的修改要頻繁得多,因此如果可以在不進行 Python 程式碼修改的情況下變更設計,那將會方便得多。 Python 程

C++ Template模板函式與模板類

函式模板擴充套件了函式過載並簡化函式的過載: 利用函式過載可以讓多個函式共享一個函式名,只要所過載的函式的引數型別必須有所不同。 但是,由於引數的型別不一樣,雖然這些函式所完成的功能完全一樣,也必須為每一個過載函式編寫程式碼。 類模板實際上是函式模板的推廣, 它是一種不確定類的某些資料成員的

c++ template traits

看了好久的c++ templates,是時候總結一下了, 從traits 開始講起, 往後會把所學到的東西一一寫上。 首先我們從一個例項開始,對一個數組進行一個累加操作。可如此做。 考慮一些比較特殊的情況,比如說一個char的陣列,要對其求和。如果其值超過了255就會溢位,那麼顯

C++ Template 中的typename、class關鍵字【轉】

在c++Template中很多地方都用到了typename與class這兩個關鍵字,而且好像可以替換,是不是這兩個關鍵字完全一樣呢?         相信學習C++的人對class這個關鍵字都非常明白,class用於定義類,在模板引入c++後,最初定義模板的方法為:tem

C++ Template (二):初步超程式設計

前言   在上一篇部落格C++ Template (一):模板基礎中,簡單介紹了模板的定義,例項化,特化以及引數包的使用,在一些簡單的場景中,已經可以通過這些知識去大展手腳了。但是想真正發揮Templa

[C++ Template]基礎--非型別模板引數

目錄 4.4 小結 4 非型別模板引數 對於函式模板和類模板, 模板引數並不侷限於型別, 普通值也可以作為模板引數。 在基於型別引數的模板中, 你定義了一些具體細節未加確定的程式碼, 直到程式碼被呼叫時這些細節才被真正確定。 然而, 在這裡, 我們面

[轉]C++ template —— 模板基礎(一)

分配 博客 講解 變參 ice allocator 不可 nts 枚舉值 《C++ Template》對Template各個方面進行了較為深度詳細的解析,故而本系列博客按書本的各章順序編排,並只作為簡單的讀書筆記,詳細講解請購買原版書籍(絕對物超所值)。--------

C++ template 讀書筆記

第九章 9.2.1 ADL 如果把被呼叫函式的名稱(如max)用圓括號括起來,也不會使用ADL。 用VS08試了試,確實如此。 要說明的是兩點: 1. “(如max)”這個是譯者加的,並且加的非常不合適。因為在上文給的例子中,max本來就不需要ADL查詢。需要ADL查詢的