变长参数模板 和 外部模板
變長參數(shù)模板
解釋
C++03只有固定模板參數(shù)。C++11 加入新的表示法,允許任意個數(shù)、任意類別的模板參數(shù),不必在定義時將參數(shù)的個數(shù)固定。
變長模板、變長參數(shù)是依靠C++11新引入的參數(shù)包的機制實現(xiàn)的。
?
參數(shù)包
-
一個模板形參包(template parameter pack)是一個接受零個或多個模板實參的模板形參。
-
一個函數(shù)形參包(function parameter pack)是一個接受零個或多個函數(shù)實參的函數(shù)形參
-
一個形參包要么是一個模板形參包,要么是一個函數(shù)形參包。
-
一個包擴展(expansion)由一個模式(pattern)和一個省略號組成。包擴展的實例中一個列表中產(chǎn)生零個或多個模式的實例。模式的形式依賴于擴展所發(fā)生的上下文中
解包
一個常用的技巧是:利用模板推導機制,每次從參數(shù)包里面取第一個元素,縮短參數(shù)包,直到包為空。
template <typename T>void fun(const T& t){ cout << t << '\n';}template <typename T, typename ... Args>void fun(const T& t, Args ... args){ cout << t << ',';??fun(args...);//遞歸解決,利用模板推導機制,每次取出第一個,縮短參數(shù)包的大小。}在C++17標準中,可以使用fold expression,更直接地表達,并且確保正序展開:
//?C++17template<typename T0, typename... T>void printf(T0 t0, T... t) { std::cout << t0 << std::endl; if constexpr (sizeof...(t) > 0) printf(t...);}// C++11#include <iostream>template <typename T0>void printf(T0 value){ std::cout << value << std::endl;}template <typename T, typename... Args>void printf(T value, Args... args){ std::cout << value << std::endl; printf(args...);}int main(){ printf(1, 2, "123", 1.1); return 0;}外部模板
關鍵詞
extern
語法
extern?template?class|struct?模板名?<?實參列表?>?;????
解釋
類模板自身并不是類型、對象或任何其他實體。不會從僅含模板定義的源文件生成任何代碼。必須實例化模板以令任何代碼出現(xiàn):必須提供模板實參,使得編譯器能生成實際的類(或從函數(shù)模板生成函數(shù))。
如果外部模板聲明出現(xiàn)于某個編譯單元中,那么與之對應的顯式實例化必須出現(xiàn)于另一個編譯單元中或者同一個編譯單元的后續(xù)代碼中;
外部模板不能用于一個靜態(tài)函數(shù)(沒有外部鏈接屬性),但可以用于類靜態(tài)成員函數(shù)。
類模板實例化分為兩種:顯示實例化和隱式實例化。
顯示實例化有如下方法:
template class|struct 模板名 < 實參列表 > ;
extern template class|struct 模板名 < 實參列表 > (C++11 起);
第二種方法就是我們要說的C++11新增的方法。
隱式實例化:
當代碼在要求完整定義的類型的語境中涉指某個模板時,或當類型的完整性對代碼有影響,而這個特定類型尚未被顯式實例化時,發(fā)生隱式實例化。例如當構造此類型的對象之時,但不包括構造指向此類型的指針之時。舉個例子:
template<class T> struct Z { void f() {} void g(); // 并不定義}; // 模板定義template struct Z<double>; // 顯式實例化 Z<double>Z<int> a; // 隱式實例化 Z<int>Z<char>* p; // 此處不實例化任何內(nèi)容p->f(); // 隱式實例化 Z<char> 而 Z<char>::f() 出現(xiàn)于此。// 并不需要且始終不實例化 Z<char>::g():不必對其進行定義WHY
而對于函數(shù)模板來說,現(xiàn)在我們遇到的問題和extern一個變量遇到的問題相同。不同的是,發(fā)生問題的不是變量(數(shù)據(jù)),而是函數(shù)(代碼)。這樣的困境是由于模板的實例化帶來的。
比如,我們在一個test.h的文件中聲明了如下一個模板函數(shù):
template <typename T> void fun(T) {}在第一個test1.cpp文件中,我們定義了以下代碼:
#include "test.h"void test1() { fun(3); }而在另一個test2.cpp文件中,我們定義了以下代碼:
#include "test.h"void test2() { fun(4); }由于兩個源代碼使用的模板函數(shù)的參數(shù)類型一致,所以在編譯test1.cpp的時候,編譯器實例化出了函數(shù) fun(int),而當編譯test2.cpp的時候,編譯器又再一次實例化出了函數(shù)fun(int)。那么可以想象,在test1.o目標文件和test2.o目標文件中,會有兩份一模一樣的函數(shù)fun(int)代碼。
?
代碼重復和數(shù)據(jù)重復不同。數(shù)據(jù)重復,編譯器往往無法分辨是否是要共享的數(shù)據(jù);而代碼重復,為了節(jié)省空間,保留其中之一就可以了(只要代碼完全相同)。事實上,大部分鏈接器也是這樣做的。在鏈接的時候,鏈接器通過一些編譯器輔助的手段將重復的模板函數(shù)代碼fun(int)刪除掉,只保留了單個副本。這樣一來,就解決了模板實例化時產(chǎn)生的代碼冗余問題。
?
不過讀者也注意到了,對于源代碼中出現(xiàn)的每一處模板實例化,編譯器都需要去做實例化的工作;而在鏈接時,鏈接器還需要移除重復的實例化代碼。很明顯,這樣的工作太過冗余,而在廣泛使用模板的項目中,由于編譯器會產(chǎn)生大量冗余代碼,會極大地增加編譯器的編譯時間和鏈接時間。解決這個問題的方法基本跟變量共享的思路是一樣的,就是使用“外部的”模板。
C++11我們可以通過下面代碼來實現(xiàn)顯示實例化:
extern template void fun<int>(int);這樣一來,在test2.o中不會再生成fun(int)的實例代碼。由于test2.o不再包含fun(int)的實例,因此鏈接器的工作很輕松,基本跟外部變量的做法是一樣的,即只需要保證讓test1.cpp和test2.cpp共享一份代碼位置即可。而同時,編譯器也不用每次都產(chǎn)生一份fun(int)的代碼,所以可以減少編譯時間。這里也可以把外部模板聲明放在頭文件中,這樣所有包含test.h的頭文件就可以共享這個外部模板聲明了。這一點跟使用外部變量聲明是完全一致的。
總結(jié)
以上是生活随笔為你收集整理的变长参数模板 和 外部模板的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 变量之--列表初始化和结构化绑定
- 下一篇: using(别名)和range base