クラステンプレート
CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 65/104。
関数テンプレートを使うと型に依存しない関数を記述できるのと同様に、クラステンプレートを使うと型に依存しないクラスを作成できます。std::vectorのようなコンテナはこの仕組みで動作します。構造を一度定義すると、コンパイラが使用する型ごとに特定のバージョンを生成します。
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // こんにちは
}クラステンプレートからオブジェクトを作成するときは、通常、山括弧内に型を指定します。C++17以降は、コンパイラがコンストラクターの引数から型を推論することもできます(Box b(42);はBox<int>を作成します)。ただし、型を推論するための引数がない場合は、型を記述する必要があります。すると、コンパイラはその特定の型に対するクラス定義全体を生成します。
クラステンプレートには複数の型パラメーターを指定でき、整数のような非型パラメーターを指定することもできます。
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // 5個のdouble型の値を格納する配列
arr[0] = 3.14;クラスの外部でメンバー関数を定義するときは、テンプレート宣言を繰り返す必要があります。
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }クラステンプレートは、C++におけるジェネリックプログラミングの基盤であり、あらゆる型で動作する再利用可能なデータ構造を記述できます。
チャレンジ
簡単クラステンプレートを使って汎用ストレージシステムを作成し、あらゆる型のデータを格納できる柔軟なコンテナーを作りましょう。テンプレートクラスをヘッダーファイルに定義し、メインプログラムでさまざまな型を使ってその汎用性を示します。
次の2つのファイルを作成します。
Storage.h:単一の値を格納するシンプルなコンテナーとして機能する、Storageというクラステンプレートを定義します。便利な操作もいくつか用意します。Storageテンプレートには、次の要素が必要です。- 格納する値を保持するprivateメンバー
- 初期値を受け取るコンストラクター
- 格納された値を返す
getValue()メソッド - 格納された値を更新する
setValue()メソッド - 値が型Tのデフォルト構築値と等しい場合は
trueを返し、それ以外の場合はfalseを返すisEmpty()メソッド
また、関連する2つの値を格納する、2つの型パラメーターを持つ
Pairというクラステンプレートも作成します。- 1つ目と2つ目の値(異なる型でもかまいません)を保持するprivateメンバー
- 両方の値を初期化するコンストラクター
- それぞれの値を取得する
getFirst()とgetSecond()メソッド (<first>, <second>)を出力するdisplay()メソッド
main.cpp:4つの入力を読み込みます(それぞれ別の行に入力)。- 整数値
- 新しい整数値
- 文字列値
- double値
次のようにしてテンプレートの使い方を示します。
- 最初の整数を使って
Storage<int>を作成し、Int storage: <value>を出力する setValue()を使って2つ目の整数で値を更新し、その後Updated: <value>を出力する- 文字列入力を使って
Storage<std::string>を作成し、String storage: <value>を出力する - 文字列のストレージが空かどうかを確認し、
Is empty: <true/false>を出力する(trueまたはfalseを出力する) - 文字列値とdouble値を使って
Pair<std::string, double>を作成し、次にdisplay()を呼び出す - 両方の整数入力を使って
Pair<int, int>を作成し、次にdisplay()を呼び出す
たとえば、入力が42、100、Hello、3.14の場合:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)同じStorageテンプレートが整数と文字列の両方で問題なく動作し、Pairを使えば異なる型を組み合わせられることに注目してください。コンパイラーは、使用する型の組み合わせごとに個別のクラス定義を生成します。単一の型を使うテンプレートにはtemplate <typename T>構文を、複数の型パラメーターを使うテンプレートにはtemplate <typename T, typename U>構文を使うことを忘れないでください。
自分で試してみよう
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// 入力を読み取る
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: 最初の整数でStorage<int>を作成し、Int storage: <value>を出力する
// TODO: setValue()を使って2番目の整数に更新し、Updated: <value>を出力する
// TODO: 入力された文字列でStorage<std::string>を作成し、String storage: <value>を出力する
// TODO: 文字列のストレージが空かどうかを確認し、Is empty: true/falseを出力する
// TODO: 文字列とdoubleでPair<std::string, double>を作成し、display()を呼び出す
// TODO: 2つの整数でPair<int, int>を作成し、display()を呼び出す
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタパラメータ付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ徹底解説Rule of Three / Five / Zero復習 - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () の演算子オーバーロード型変換演算子復習 - Matrixクラス自分で練習してみよう: C++オンラインコンパイラ