[C++ Template]基礎--非型別模板引數
目錄
4 非型別模板引數
對於函式模板和類模板, 模板引數並不侷限於型別, 普通值也可以作為模板引數。 在基於型別引數的模板中, 你定義了一些具體細節未加確定的程式碼, 直到程式碼被呼叫時這些細節才被真正確定。 然而, 在這裡, 我們面對的這些細節是值(value) , 而不是型別。 當要使用基於值的模板時, 你必須顯式地指定這些值, 才能夠對模板進行例項化, 並獲得最終程式碼。
4.1 非型別的類模板引數
較之前一章stack例子的實現, 你也可以使用元素數目固定的陣列來實現stack。用固定大小的陣列的優點是: 無論是由你來親自管理記憶體, 還是由標準容器來管理記憶體, 都可以避免記憶體管理開銷。然而, 決定一個棧(stack) 的最佳容量是很困難的。 如果你指定的容量太小, 那麼棧可能會溢位; 如果指定的容量太大, 那麼可能會不必要地浪費記憶體。 一個好的解決方法就是: 讓棧的使用者親自指定陣列的大小,並把它作為所需要的棧元素的最大個數。
為了做到這一點, 你需要把陣列大小定義為一個模板引數:
template <typename T, int MAXSIZE> class Stack { private: T elems[MAXSIZE]; // 包含元素的陣列 int numElems; // 元素的當前總個數 public: Stack(); // 建構函式 void push(T const&); // 壓入元素 void pop(); // 彈出元素 T top() const; // 返回棧頂元素 bool empty() const { // 返回棧是否為空 return numElems == 0; } bool full() const { // 返回棧是否已滿 return numElems == MAXSIZE; } }; // 建構函式 template <typename T, int MAXSIZE> Stack<T, MAXSIZE>::Stack() : numElems(0) // 初始時棧不含元素 { // 不做任何事情 } template <typename T, int MAXSIZE> void Stack<T, MAXSIZE>::push(T const& elem) { if (numElems == MAXSIZE) { throw std::out_of_range("Stack<>::push(): stack is full"); elems[numElems] = elem; // 附加元素 ++numElems; // 增加元素的個數 } template<typename T, int MAXSIZE> void Stack<T, MAXSIZE>::pop() { if (numElems <= 0) { throw std::out_of_range("Stack<>::pop(): empty stack"); } --numElems; // 減少元素的個數 } template <typename T, int MAXSIZE> T Stack<T, MAXSIZE>::top() const { if (numElems <= 0) { throw std::out_of_range("Stack<>::top(): empty stack"); } return elems[numElems - 1]; // 返回最後一個元素 }
MAXSIZE是新加入的第2個模板引數, 型別為int; 它指定了陣列最多可包含的棧元素的個數。
為了使用這個類模板, 你需要同時指定元素的型別和個數(即棧的最大容量):
int main() { try { Stack<int, 20> int20Stack; // 可以儲存20個int元素的棧 Stack<int, 40> int40Stack; // 可以儲存40個int元素的棧 Stack<std::string, 40> stringStack; // 可儲存40個string元素的棧 // 使用可儲存20個int元素的棧 int20Stack.push(7); std::cout << int20Stack.top() << std::endl; int20Stack.pop(); // 使用可儲存40個string的棧 stringStack.push("hello"); std::cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch(std::exception const& ex) { std::cerr << "Exception: " << ex.what() << std::endl; return EXIT_FAILURE; // 退出程式且有ERROR標記 } }
可以看出, 每個模板例項都具有自己的型別, 因此 int20Stack 和int40Stack 屬於不同的型別, 而且這兩種型別之間也不存在顯式或者隱式的型別轉換; 所以它們之間不能互相替換, 更不能互相賦值。
同樣, 我們可以為模板引數指定預設值:
template<typename T = int, int MAXSIZE = 100>
class Stack
{
...
};
4.2 非型別的函式模板引數
你也可以為函式模板定義非型別引數。 例如, 下面的函式模板定義了一組用於增加特定值的函式:
template<typename T, int VAL>
T addValue(T const& x)
{
return x + VAL;
}
如果需要把函式或者操作用作引數的話, 那麼這類函式就是相當有用的。 譬如, 藉助於STL, 你可以傳遞這個函式模板的例項化體給集合中的每一個元素, 讓它們都增加一個整數值:
std::transform(source.begin(), source.end(), //源集合的起點和終點
dest.begin(), //目標集合的起點
addValue<int, 5>);
4.3 非型別模板引數的限制
非型別模板引數是有限制的。 通常而言, 它們可以是常整數(包括列舉值) 或者指向外部連結物件的指標。浮點數和類物件是不允許作為非型別模板引數的。
template<double VAT> //ERROR:浮點數不能作為非型別模板引數
double process(double v)
{
return v * VAT;
}
template<std::string name> //ERROR:類物件不能作為非型別模板引數
class MyClass
{
...
};
由於字串文字是內部連結物件(因為兩個具有相同名稱但處於不同模組的字串, 是兩個完全不同的物件) , 所以你不能使用它們來作為模板實參:
template<char const* name>
class MyClass
{
};
MyClass<"hello"> x; //ERROR:不允許使用字串文字"hello"
另外, 你也不能使用全域性指標作為模板引數:
template <char const* name>
class MyClass
{
};
char const* s = "hello";
MyClass<s> x; //s是一個指向內部連結物件的指標
然而,你可以這樣使用:
template <char const* name>
class MyClass
{
};
extern char const s[] = "hello";
MyClass<s> x; //OK
全域性字串陣列s由"hello"初始化,是一個外部連線物件。
4.4 小結
•模板可以具有值模板引數, 而不僅僅是型別模板引數。
•對於非型別模板引數, 你不能使用浮點數、 class 型別的物件和內部連結物件(例如string) 作為實參。