天天看點

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

文章目錄

  • 為什麼要有模闆?
  • 函數模闆
    • 函數模闆的格式
    • 函數模闆原理
    • 函數模闆執行個體化
    • 模闆參數比對
  • 類模闆
    • 定義格式
    • 類模闆執行個體化
  • 模闆特化
    • 全特化 & 偏特化
  • 模闆的分離編譯

為什麼要有模闆?

如果要交換兩個值,如果兩位數都是(int,int)則需要調用下面第一個函數

void Swap(int &left, int &right)

,如果要交換的是(double,double)則要調用的是

void Swap(double &left, double &right)

。那麼如果需要調用别的float,long等類型就需要在此實作函數重載,雖然也可以實作,但是相對代碼的複用率比較低,代碼維護性也比較低效。于是就有了泛函程式設計。

void Swap(int &left, int &right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double &left, double &right)
{
	double temp = left;
	left = right;
	right = temp;
}
           

泛函程式設計:編寫與類型沒有關系的通用代碼,是代碼複用的一種手段。模闆作為泛函程式設計的基礎。對于模闆有以下兩種,函數模闆和類模闆。

函數模闆

函數模闆是一個函數家族,該函數模闆與類型無關,在使用過程中被參數化,根據調用的實參類型産生函數的特定類型的版本。

函數模闆的格式

template <typename T1, typename T2, ... , typename Tn>

傳回值類型 函數名 (參數清單){}

這裡将上述的交換函數寫成函數模闆為下面形式:

template<typename T> //定義模闆參數T可使用typename或class
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}
           

注釋:typename可以換成class 但是 class不能換成struct。

函數模闆原理

函數模闆本質不是函數,是編譯器用使用方式産生特定具體類型函數的模具。模闆也就是說将我們該做的重複的事交給編譯器去完成。

檢視下圖,仔細分析發現調用函數的模闆實作交換,但是調用函數的位址不一樣,可以說明低層調用的是不同的函數。

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

總結:

在編譯器階段,對于模闆函數的使用,編譯器是依據傳入的實參類型來推演出對應類型的函數以供調用。(當我們使用int類型去調用函數模闆時,編譯器根據實參類型推演,将T确定為int類型,然後産生一份用來處理int類型的代碼,就如上圖call不同的位址的函數)。

函數模闆執行個體化

函數模闆執行個體化:是用不用類型的參數使用函數模闆。

模闆參數執行個體化:

  1. 隐式執行個體化
  2. 顯示執行個體化。

隐式執行個體化:

(編譯器根據實參推演模闆參數的實際類型)

顯示執行個體化:

(文法:函數名<指定的類型>(實參清單) 。)

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

模闆參數比對

  1. 一個非模闆函數可以和函數模闆同時存在,該函數模闆仍然可以被執行個體化為這個非模闆函數。
  2. 對于非模闆函數和函數模闆同時存在時,在同樣情況下,一般情況下,優先選擇調用非模闆函數,而不用選用函數模闆來去執行個體化一個函數,除非此函數模闆可以執行個體化出來更好的比對的函數。

    簡述:有現成的比對的函數,就調用現成的。執行個體化的更合适就選擇比對執行個體化出來的函數。

  3. 函數模闆不允許自動類型的轉換,這點普通函數可以實作自動類型轉換。

類模闆

定義格式

template <class T1,class T2,...,class Tn>
class 類模闆名
{
	//類内的成員函數和成員變量
} 
           

類模闆實作

namespace yumoz
{
	template <class T>
	class vector
	{
	public:
		vector()
			:_a(nullptr)
			, _size(0)
			, _capacity(0)
		{}

		~vector()
		{
			delete[] _a;
			_a = nullptr;
			_size = _capacity = 0;
		}

		void push_back(const T& x)
		{
			if (_size == _capacity)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				T* temp = new T[newcapacity];
				if (_a)
				{
					memcpy(temp, _a, sizeof(T)*_size);
					delete[] _a;
				}
				_a = temp;
				_capacity = newcapacity;
			}
			_a[_size] = x;
			++_size;
		}

		//類内實作
	/*	T& operator[](size_t pos)
		{
			assert(pos < _size);
			return _a[pos];
		}
		size_t size()
		{
			return _size;
		}*/

		//類外實作 部分1
		T& operator[](size_t pos);
		size_t size();

	private:
		T* _a;
		size_t _size;
		size_t _capacity;
	};

	//類外實作  部分2
	template<class T>//不可省略
	T& vector<T>::operator[](size_t pos)
	{
		assert(pos < _size);
		return _a[pos];
	}
	template<class T>//不可省略
	size_t vector<T>::size()
	{
		return _size;
	}
}

int main()
{
	yumoz::vector<int>v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	//v1.operator[](3)
	for (size_t i = 0; i < v1.size(); ++i)
	{
		v1[i] *= 3;//擴大三倍,傳回值是引用
	}

	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;

	yumoz::vector<double>v2;
	v2.push_back(1.1);
	v2.push_back(2.2);
	for (size_t i = 0; i < v2.size(); ++i)
	{
		cout << v2[i] << " ";
	}
	cout << endl;

	return 0;
}
           

類模闆執行個體化

類模闆執行個體化需要在類模闆名字後跟上<>,然後将執行個體化的類型放在<>中即可,類模闆名字不是真正的類,而執行個體化的結果是真正的類。

vector<int>v1;
vector<double>v2;
           

模闆特化

先看一下下面這個圖:

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

看上圖,明顯發現明明都是“hello yumoz”,但是比較結果竟然不一樣。問題出在哪了???此時就需要對模闆進行特化處理。特化後結果如下:

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

全特化 & 偏特化

全特化就是對模闆參數清單中所有的參數都确定化;偏特化(或半特化)有部分特化(将模闆參數類清單中的一部分參數特化)還有參數更進一步限制(偏特化并不僅僅是指特化部分參數,而是針對模闆參數更進一步的條件限制所設計出來的一個特化版本)

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯
C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

模闆的分離編譯

分離編譯?

一個項目由若幹源檔案共同實作,而每個源檔案單獨編譯生成目标檔案,最後将目标檔案連結形成單一的可執行檔案的過程稱之為分離編譯模式。

一圖看懂模闆的分類編譯改變辦法:

C++模闆(函數模闆、類模闆、模闆特化、分離編譯)為什麼要有模闆?函數模闆類模闆模闆特化模闆的分離編譯

編譯時隻有聲明沒有函數定義;

隻能确定函數名稱,檢查參數比對,但沒有函數位址,位址連結時再去找;

模闆分離編譯,定義的地方不執行個體化;執行個體化的地方沒有定義,隻有聲明。

繼續閱讀