関数テンプレートは、関数のファミリを表します。したがって、クラス テンプレートはクラスのファミリを表します。今日は、クラス テンプレートを紹介したいと思います。
クラス テンプレートの定義は簡単です。
クラス テンプレートの定義
クラス Array
があるとします。 それがクラス テンプレートになるはずです。
class Array{ public: int getSize() const { return 10; } private: int elem[10]; };
クラス Array
長さ 10 の int の C 配列を保持します。C 配列の型とその長さは、明らかな一般化ポイントです。型パラメータ T
を導入してクラステンプレートを作ってみよう および非型パラメーター N
// arrayClassTemplate.cpp #include <cstddef> // (1) #include <iostream> #include <string> template <typename T, std::size_t N> // (2) class Array{ public: std::size_t getSize() const { return N; } private: T elem[N]; }; int main() { std::cout << '\n'; Array<int, 100> intArr; // (3) std::cout << "intArr.getSize(): " << intArr.getSize() << '\n'; Array<std::string, 5> strArr; // (4) std::cout << "strArr.getSize(): " << strArr.getSize() << '\n'; Array<Array<int, 3>, 25> intArrArr; // (5) std::cout << "intArrArr.getSize(): " << intArrArr.getSize() << '\n'; std::cout << '\n'; }
Array
タイプとサイズによってパラメーター化されます。サイズについては、符号なし整数型 std::size_t
を使用しました (2)最大サイズまで収納可能。 std::size_t
を使用するには 、ヘッダー <cstddef>
を含める必要があります (1)。これまでのところ、Array
int
でインスタンス化できます (3)、std::string
(4)、および Array<int, 3>
を使用 (5)。次のスクリーンショットは、プログラムの出力を示しています。
テンプレートのメンバー関数は、クラス テンプレートの内外で定義できます。
メンバー関数の定義
クラステンプレート 内でメンバー関数を定義する
template <typename T, std::size_t N> class Array{ public: std::size_t getSize() const { return N; } private: T elem[N]; };
クラスの外部でメンバー関数を定義する場合は、それがテンプレートであることを指定する必要があり、クラス テンプレートの完全な型修飾を指定する必要があります。変更されたクラス テンプレート Array
は次のとおりです。 :
template <typename T, std::size_t N> class Array{ public: std::sizt_ getSize() const; private: T elem[N]; }; template <typename T, std::size_t N> // (1) std::size_t Array<T, N>::getSize() const { return N; }
(1) はメンバ関数 getSize
です Array
の 、クラス外で定義されています。メンバー関数自体がテンプレートである場合、クラス テンプレートの外部でメンバー関数を定義することは非常に困難になります。
テンプレートとしてのメンバー関数
ジェネリック メンバー関数の典型的な例は、テンプレート化された代入演算子です。理由は簡単です。 Array<T, N>
を割り当てたい Array<T2, N2>
に T
の場合 T2
に割り当て可能 両方の配列が同じサイズです。
Array<float, 5>
を割り当てる Array<double, 5>
に 両方の配列の型が異なるため、無効です。
// arrayAssignmentError.cpp #include <cstddef> #include <iostream> #include <string> template <typename T, std::size_t N> class Array{ public: std::size_t getSize() const { return N; } private: T elem[N]; }; int main() { std::cout << '\n'; Array<float, 5> floatArr; Array<float, 5> floatArr2; floatArr2 = floatArr; // (1) Array<double, 5> doubleArr; doubleArr = floatArr; // (2) }
floatArr
を割り当てる floatArr2
へ (1) は、両方の配列が同じ型であるため有効です。 floatArr
を割り当てる doubleArr
まで 両方のクラスが異なるタイプであるため、エラー (2) です。その結果、コンパイラは Array<float, 5>
からの変換がないことを訴えます Array<double, 5>.
に
これは、同じ長さの 2 つの配列の割り当てをサポートするクラス Array の単純な実装です。 C 配列 elem
意図的に公開されています。
template <typename T, std::size_t N> class Array{ public: template <typename T2> Array<T, N>& operator = (const Array<T2, N>& arr) { std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem)); return *this; } std::size_t getSize() const { return N; } T elem[N]; };
代入演算子 Array<T, N>& operator = (const Array<T2, N>& arr)
基になる型が異なる可能性があるが、長さが異なる可能性がある配列を受け入れます。コードの動作を示す前に、コードを改善したいと思います。
友情
elem を非公開にするには、クラスのフレンドでなければなりません。
template <typename T, std::size_t N> class Array{ public: template <typename T2> Array<T, N>& operator = (const Array<T2, N>& arr) { std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem)); return *this; } template<typename, std::size_t> friend class Array; // (1) std::size_t getSize() const { return N; } private: T elem[N]; };
行 template<typename, std::size_t> friend class Array
(1) Array のすべてのインスタンスをフレンドに宣言します。
クラス外で定義されたメンバ関数
クラス外でジェネリック メンバ関数を定義するのは大変な作業です。
template <typename T, std::size_t N> class Array{ public: template <typename T2> Array<T, N>& operator = (const Array<T2, N>& arr); template<typename, std::size_t> friend class Array; std::size_t getSize() const; private: T elem[N]; }; template <typename T, std::size_t N> std::size_t Array<T, N>::getSize() const { return N; } template<typename T, std::size_t N> // (1) template<typename T2> Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) { std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem)); return *this; }
この場合、クラス本体の外側で汎用メンバー関数 (1) を定義し、クラスとメンバー関数がテンプレートであることを指定する必要があります。さらに、ジェネリック メンバー関数の完全な型修飾を指定する必要があります。これまでのところ、代入演算子は型 T
に使用されています と T2
変換できないもの。変換不可能な型で代入演算子を呼び出すと、醜いエラー メッセージが表示されます。これを修正する必要があります。
型パラメータの要件
要件は、型特性ライブラリと static_assert
で定式化できます (C++11)、またはコンセプト付き (C++20)。一般的な代入演算子の 2 つのバリエーションを次に示します。
- C++11
template<typename T, std::size_t N> template<typename T2> Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) { static_assert(std::is_convertible<T2, T>::value, // (1) "Cannot convert source type into the destination type!"); std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem)); return *this; }
- C++20
最後に、概念 std::convertible_to
を使用した完全なプログラムを次に示します。 メンバー関数の宣言 (1) および定義 (2) 内。
// arrayAssignment.cpp #include <algorithm> #include <cstddef> #include <iostream> #include <string> #include <concepts> template <typename T, std::size_t N> class Array{ public: template <typename T2> Array<T, N>& operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T>; // (1) template<typename, std::size_t> friend class Array; std::size_t getSize() const; private: T elem[N]; }; template <typename T, std::size_t N> std::size_t Array<T, N>::getSize() const { return N; } template<typename T, std::size_t N> template<typename T2> Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T> { // (2) std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem)); return *this; } int main() { std::cout << '\n'; Array<float, 5> floatArr; Array<float, 5> floatArr2; floatArr.getSize(); floatArr2 = floatArr; Array<double, 5> doubleArr; doubleArr = floatArr; Array<std::string, 5> strArr; // doubleArr = strArr; // (3) }
(3) を有効にすると、GCC は本質的に、制約が満たされていないと不平を言います。
次は?
ご想像のとおりです。私はクラス テンプレートを使い果たしたわけではありません。次回は、クラス テンプレートの継承と、クラス テンプレートのメンバー関数のインスタンス化という 2 つのトリッキーな詳細について書きます。
次の PDF バンドル
古いサービスを復活させ、古い投稿に関するバンドルを作成したい。これはかなりの仕事なので、英語の投稿用にのみバンドルを作成します。これらのバンドルには、投稿、すべてのソース ファイル、および cmake ファイルが含まれます。私が正しい決断を下すためには、あなたが十字架を負わなければなりません。最も投票数の多い pdf バンドルをビルドします。投票は 30.05 (含む) まで開いています。ここで投票してください。