[C++] 初めてのテンプレート入門

1. 汎用プログラミング

交換関数を実装し、さまざまなタイプのパラメーター実装をサポートしたいとします。次のコードのように、typedefを使用して型の名前を変更できます。

		// 将 int 起别名为 DataType
		typedef int DataType;
		
		void Swap(DataType& x, DataType& y)
		{
			DataType tmp = x;
			x = y;
			y = tmp;
		}
		
		int main()
		{
			DataType x = 0, y = 6;
			Swap(x, y);
		
			return 0;
		}

このようにして、型を変更する必要があるたびに、int を別の型に変更するだけで済みます。

上記は 1 つの方法ですが、関数のオーバーロードを使用して実装できる別の方法もあります。たとえば、次のとおりです。

		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;
		}
		
		void Swap(char& left, char& right)
		{
			char temp = left;
			left = right;
			right = temp;
		}

上記 2 つの方法はユニバーサル交換機能を実装できますが、次のような欠点があります。

  1. オーバーロードされた関数は型が異なるだけでコードの再利用率が比較的低く、新しい型が出現する限り、ユーザーは対応する関数を追加するか型を変更する必要があります。
  2. コードの保守性は比較的低く、1 つのエラーがすべてのオーバーロードでエラーを引き起こす可能性があります。

コンパイラにテンプレートを指示し、コンパイラがそのテンプレートを使用してさまざまなタイプに応じたコードを生成できるようにすることはできますか? 答えは「はい、ここではジェネリック プログラミングを導入する必要があります。ジェネリック プログラミング:型に依存しないユニバーサル コードを記述することは、コードを再利用する手段です。」テンプレートは汎用プログラミングの基礎です。

テンプレートは、関数テンプレートクラス テンプレートに分かれています

2. 関数テンプレート

1. 関数テンプレートの概念

関数テンプレートは、型に依存しない関数のファミリーを表し、実際のパラメーターの型に基づいて関数の型固有のバージョンを生成するために使用される場合にパラメーター化されます。

2. 関数テンプレートの形式

関数テンプレートを定義する前に、キーワードtemplateを導入する必要があります。これは、テンプレートを定義するためのキーワードです。

使用形式: template<typename T1, typename T2,......,typename Tn>テンプレートキーワードの後のテンプレート パラメーターを山括弧で囲みます。テンプレート パラメーターの数は任意ですが、typename キーワードを使用してテンプレートパラメーターを定義する必要があります。または、classを使用することもできます (注意:クラスの代わりに構造体を使用してください)。

たとえば、交換関数の関数テンプレートは次のとおりです。

		template<typename T>
		void Swap(T& t1, T& t2)
		{
			T tmp = t1;
			t1 = t2;
			t2 = tmp;
		}

3. 関数テンプレートの原理

関数テンプレートは設計図であり、関数そのものではなく、コンパイラが特定の種類の関数を生成するために使用する型です。つまり、実際には、テンプレートは、実行する必要がある繰り返しの処理をコンパイラーに渡すことになります。

たとえば、次の図はこのプロセスをよく示しています。

ここに画像の説明を挿入します

コンパイラーのコンパイル段階では、テンプレート関数を使用するために、コンパイラーは、渡される実際のパラメーターの型に基づいて、呼び出しに対応する型の関数を推測して生成する必要があります。

例: double型の関数テンプレートを使用する場合、コンパイラは実際のパラメータ type の演繹を通じてT をdouble型として決定し、次にdouble型を特別に処理するコードを生成します。同じことが文字型にも当てはまります。つまり、コンパイラはテンプレート インスタンスを使用して、対応する Swap 関数を生成します。

4. 関数テンプレートのインスタンス化

関数テンプレートが異なる型のパラメーターとともに使用される場合、それは関数テンプレートのインスタンス化と呼ばれます。テンプレート パラメーターのインスタンス化は、暗黙的なインスタンス化明示的なインスタンス化に分けられます

暗黙的なインスタンス化: 実際のパラメーターに基づいて、コンパイラにテンプレート パラメーターの実際の型を推定させます。たとえば、次のAdd関数テンプレートは 2 つの数値の加算を実装します。

		template<class T>
		T Add(T a, T b)
		{
			return a + b;
		}
		
		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
		
			cout << "sum = " << Add(a, b) << endl;
			cout << "sum = " << Add(c, d) << endl;
		
			return 0;
		}

上記 2 つの呼び出しのインスタンス化には問題はありませんが、コンパイラは暗黙的なインスタンス化を実行し、実行結果は次のようになります。

ここに画像の説明を挿入します

しかし、次のように呼び出された場合にコンパイルされますか: Add(a, d)、答えはノーです。T は実際のパラメータaを通じてintであると推定され、T は実際のパラメータdを通じてdouble型であると推定されますが、テンプレート パラメータ リストにはTが 1 つだけあります。となり、コンパイラはこれを判断できません。最後に、 T がint型またはdouble 型であると判断され、エラーが報告されます。

したがって、現時点では 2 つの解決策があります。

  1. ユーザー自身がコンバージョンを強制する
  2. 明示的なインスタンス化を使用する

自分で変換を強制したい場合は、次の方法を使用できます。

		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
			
			cout << "sum = " << Add((double)a, d) << endl;
			cout << "sum = " << Add(a, (int)d) << endl;
		
			return 0;
		}

Add関数を呼び出すときに、 aをdoubleに強制したりdをintに強制したりできます

明示的なインスタンス化:関数名の後の <> 内にテンプレート パラメーターの実際の型を指定します。

たとえば、上記の問題では、明示的なインスタンス化を使用して問題を解決しています。コードは次のとおりです。

		int main()
		{
			int a = 10, b = 20;
			double c = 1.11, d = 2.22;
		
			cout << "sum = " << Add(a, b) << endl;
			cout << "sum = " << Add(c, d) << endl;
		
			cout << "sum = " << Add<double>(a, d) << endl;
			cout << "sum = " << Add<int>(a, d) << endl;
		
			return 0;
		}

関数名の後に山括弧を使用して、テンプレート パラメーターの型を指定します。これが明示的なインスタンス化です。

注: 型が一致しない場合、コンパイラは暗黙的な型変換を実行しようとします。変換が成功しない場合、コンパイラはエラーを報告します。

5. テンプレートパラメータのマッチング原則

非テンプレート関数同じ名前の関数テンプレートの場合、他の条件が同じ場合、呼び出し時に非テンプレート関数が最初に呼び出され、テンプレートからインスタンスは生成されません。より適切に一致する関数を生成できる場合、テンプレートが選択されます。

たとえば、次の 2 つのコードは次のとおりです。

		// 专门处理int的加法函数
		int Add(int a, int b)
		{
			cout << "int Add(int a, int b)" << endl;
			return a + b;
		}
		
		// 通用加法函数
		template<class T>
		T Add(T a, T b)
		{
			cout << "T Add(T a, T b)" << endl;
			return a + b;
		}
		
		int main()
		{
			// 与非函数模板类型完全匹配,不需要函数模板实例化
			Add(1, 2); 
		
			// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的 Add 函数
			Add<int>(1, 2); 
		
			return 0;
		}

3. クラステンプレート

ユニバーサル スタックを実装する必要があるとします。typedefキーワードを使用して、型のエイリアスを作成できます。型を変更する必要があるときは、常にtypedefを変更するだけで済みます。たとえば、次のStackクラス:

		typedef int DataType;
		class Stack
		{
		public:
			Stack(size_t capacity = 4)
			{
				_array = new DataType[capacity];
		
				_capacity = capacity;
				_size = 0;
			}
		
			~Stack()
			{
				cout << "~Stack()" << endl;
		
				delete[] _array;
				_array = nullptr;
				_size = _capacity = 0;
			}
		
		private:
			// 内置类型
			DataType* _array;
			int _capacity;
			int _size;
		};

上記のStackクラスのさまざまな型はtypedef型を変更するだけで済みますが、同時に 2 つのスタックが必要で、1 つのスタックのパラメータがintで、もう 1 つのスタックのパラメータがdoubleである場合、上記のメソッドは機能しません。満足したので、クラス テンプレートを紹介します。

クラス テンプレートの使用方法は次のとおりです。例としてStackクラスを取り上げます。

		template<class T>
		class Stack
		{
		public:
			Stack(size_t capacity = 4)
			{
				_array = new T[capacity];
		
				_capacity = capacity;
				_size = 0;
			}
		
			~Stack()
			{
				delete[] _array;
				_array = nullptr;
				_size = _capacity = 0;
			}
		private:
			T* _array;
			int _capacity;
			int _size;
		};

インスタンス化されたオブジェクトは次のとおりです。

		int main()
		{
			Stack<int> st1;
			Stack<double> st2;
		
			return 0;
		}

Stackはクラス名、Stack<int>Stack<double>タイプであることに注意してください。テンプレートのスコープはStackクラスです。

このようにして、 intのパラメータを格納するスタックとdoubleを格納するスタックを 2 つ同時に実装します

おすすめ

転載: blog.csdn.net/YoungMLet/article/details/132091415