1. 程式人生 > >淺談C++ templates 函式模板、類模板以及非型別模板引數

淺談C++ templates 函式模板、類模板以及非型別模板引數

最近打算挑選幾個STL容器做個簡單實現,發現裡面牽涉到不少模板知識。這裡算提前學習一下C++模板的相關知識吧。這次主要學習了什麼是函式模板(這個最簡單),類模板以及非型別模板引數。下面挨個舉例說明。

文章目錄


1. 函式模板

函式模板是最簡答的一個,下面就定義一個返回兩個值中最大者的函式模板:

namespace og {

template<typename T>
inline const T&
max(const T& a, const T& b) { return a > b ? a : b; } }

我有個習慣,喜歡用和系統相關庫函式同樣的函式名。那麼,為了避免不必要的麻煩,用上了自己的名稱空間。我比較喜歡og這兩個字母。

咯,使用這種方式就可以呼叫了…

cout << og::max(100, 123) << endl;
cout << og::max(1.23, 999.9) << endl;

可以在控制檯上看到這樣的內容,
123
999.9

2. 類模板

翻了翻侯捷先生的STL書籍,準備動一下手。那麼,第一步就是需要弄清楚類模板。就算原理不太清楚,至少要能看懂類模板,知道其表達的是什麼意思,從而模仿著學會如何寫類模板。與函式模板一樣,這裡也舉個例子,使用類模板定義一個棧類:

第一步: 宣告一個stack類,並加上類模板。

namespace og {

template<typename T>
class stack {
public:
	void push(const T&);
	void pop();
	T top() const;
	bool empty() const {
		return elems.empty();
	}
private:
	std::vector<T> elems;
};

}//end namespace og

第二步: 實現類成員函式。這裡需要注意,上面的stack宣告是在.h檔案中,如果在.cpp

中實現編譯器會報錯。這個問題,等我研究清楚了再補充。

目前的解決方式是,同樣在.h檔案中將類成員函式實現。實現的內容就是下面這個樣子,

namespace og {

template<typename T>
void stack<T>::push(const T& elem) {
	elems.push_back(elem);
}

template<typename T>
void stack<T>::pop() {
	if (elems.empty()) {
		throw std::out_of_range("stack<T>::pop() - empty stack");
	}
	elems.pop_back();
}

template<typename T>
T stack<T>::top() const {
	if (elems.empty()) {
		throw std::out_of_range("stack<T>::top() - empty stack");
	}
	return elems.back();
}

}//end namespace og

同樣,這裡補充一下——如何使用以上的類模板定義的類。你可以這樣,

try {
	og::stack<char> sc;
	sc.push('a');
	cout << sc.top() << endl;
	sc.pop();
	sc.pop();  //exception
}
catch (const std::exception& msg) {
	cout << msg.what() << endl;
}

之所以加上個異常捕獲,因為我這裡想測試一下throw有沒有效果。當然,如果不加上trycatch,當出現throw時程式會很不友好的崩潰掉。理論上,你的控制檯應該會打印出,
a
stack::pop() - empty stack

3. 非型別模板引數

這種模板引數,我初次見到時,覺得十分怪異。你們提前感受一下,

og::array<int, 5> ai;

從形式上來看,就是給類模板指定了型別(這個我能理解),可是後面那個5是什麼意思?
在我一番學習之後,我才知道——原來這就是非型別模板引數。同樣,我也定義了一個數組類作為例子:

namespace og {

template<typename T, unsigned int N>
class array {
public:
	array();
	T& operator[] (unsigned int index);
	constexpr unsigned int size() noexcept;
private:
	T elems[N];
	int length;
};

}//end namespace og

是的,這次模板的宣告變成這樣了template<typename T, unsigned int N>後面多了一個unsigned int N。而且讓人奇怪的是非型別模板引數好像還只能是整型的。

我嘗試了整了一個double N引數,給我報錯了——告訴我“浮點模板引數是非標準的”。

這個等以後研究清楚了,再補充。那麼,繼續放上以上類宣告的實現部分:

namespace og {

template<typename T, unsigned int N>
array<T, N>::array() : length(N) {
	memset(elems, 0, sizeof(T)*N);
}

template<typename T, unsigned int N>
T& array<T, N>::operator[](unsigned int index) {
	if (index >= size()) {
		throw std::out_of_range("array<T, N>::operator[]() - index out of range");
	}
	return elems[index];
}

template<typename T, unsigned int N>
constexpr unsigned int array<T, N>::size() noexcept {
	return length;
}

}//end namespace og

這裡,主要是實現了一個operator[]函式,當然也還有其它兩個函式。

咯,再一次看看怎麼用,又和老夥伴見面了——og::array<int, 5> ai;

try {
	og::array<int, 5> ai;
	ai[0] = 4;
	ai[2] = 123;
	for (unsigned int i = 0; i < ai.size(); ++i) {
		cout << ai[i] << endl;
	}
	cout << ai[7] << endl; //exception
}
catch (const std::exception& msg) {
	cout << msg.what() << endl;
}

最後,再來看看控制檯是什麼樣子?
4
0
123
0
0
array<T, N>::operator - index out of range

到此處還意猶未盡的話,推薦閱讀《對C++ templates類模板的幾點補充(Traits類模板特化)》這篇文章——主要講述了模板的預設引數和類模板特化的相關知識,當然還涉及一些STL迭代器的內容。



©為徑
2018-12-22 北京 海淀


Reference: C++ Templates(中文版)