Menu
CoddyTech

クラステンプレート

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++におけるジェネリックプログラミングの基盤であり、あらゆる型で動作する再利用可能なデータ構造を記述できます。

challenge icon

チャレンジ

簡単

クラステンプレートを使って汎用ストレージシステムを作成し、あらゆる型のデータを格納できる柔軟なコンテナーを作りましょう。テンプレートクラスをヘッダーファイルに定義し、メインプログラムでさまざまな型を使ってその汎用性を示します。

次の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つの入力を読み込みます(それぞれ別の行に入力)。
    1. 整数値
    2. 新しい整数値
    3. 文字列値
    4. double値

    次のようにしてテンプレートの使い方を示します。

    1. 最初の整数を使ってStorage<int>を作成し、Int storage: <value>を出力する
    2. setValue()を使って2つ目の整数で値を更新し、その後Updated: <value>を出力する
    3. 文字列入力を使ってStorage<std::string>を作成し、String storage: <value>を出力する
    4. 文字列のストレージが空かどうかを確認し、Is empty: <true/false>を出力する(trueまたはfalseを出力する)
    5. 文字列値とdouble値を使ってPair<std::string, double>を作成し、次にdisplay()を呼び出す
    6. 両方の整数入力を使って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;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ