クラス テンプレート

関数テンプレートは、関数のファミリを表します。したがって、クラス テンプレートはクラスのファミリを表します。今日は、クラス テンプレートを紹介したいと思います。

クラス テンプレートの定義は簡単です。

クラス テンプレートの定義

クラス 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 (含む) まで開いています。ここで投票してください。