1. 程式人生 > >[C++ Template]基礎--非型別模板引數

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

目錄

4.4 小結

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) 作為實參。