
C++入門|クラステンプレート
クラスそのものを型に合わせて変化させる。クラステンプレートは、どんな修行データにも対応できる万能設計図です。
前回は、関数テンプレートを使って、型が違っても同じような処理を1つにまとめる方法を学びました。
関数テンプレートでは、たとえばunite(x, y)のように書くことで、Tにintを入れれば数値の合計、Tにstringを入れれば文字列の連結として使えました。つまり、関数の中で使う型をあとから決められるようにしたわけです。
今回学ぶクラステンプレートは、その考え方をクラス全体に広げたものです。
関数だけではなく、メンバ変数の型、メンバ関数の引数の型、戻り値の型まで、まとめてテンプレート化できます。
ドラゴンボール風にたとえるなら、関数テンプレートは「1つの技だけを型に合わせて変える仕組み」でした。一方、クラステンプレートは「修行装置そのものを、入れるデータの種類に合わせて作り替える仕組み」です。
数値の修行ポイントを扱うなら、数値用の修行装置になります。
技名の文字列を扱うなら、技名連結用の修行装置になります。
同じクラスの設計図を使いながら、int用にもstring用にも変化できる。
これがクラステンプレートの大きな特徴です。
| 学習する内容 | 役割 |
|---|---|
| クラステンプレート | クラス全体を型に合わせて使い分ける |
| template | クラス内でTという型を使えるようにする |
| KiMixer | int型のデータを扱うクラスとして使う |
| KiMixer | string型のデータを扱うクラスとして使う |
| inline | 短い関数を呼び出し元へ展開しやすくする |
| const | メンバ関数がインスタンスの状態を変更しないことを示す |
クラステンプレートとは何か
クラステンプレートとは、クラスの中で使う型をあとから指定できるようにしたものです。
普通のクラスでは、メンバ変数の型をあらかじめ決めておきます。
たとえば、修行ポイントを扱うクラスなら、メンバ変数をint型にするかもしれません。
int m_n1;
int m_n2;この場合、そのクラスはint型専用になります。
一方、技名を扱うクラスなら、メンバ変数をstring型にしたくなります。
string m_n1;
string m_n2;この場合は、string型専用になります。
しかし、処理の流れが同じなら、型ごとに別々のクラスを作るのは少しもったいないです。2つの値を保存し、それらを+でまとめて返す、という考え方はintでもstringでも共通しています。
そこで、型をTとしておき、使うときにTをintやstringに置き換えられるようにします。
template<typename T>
class KiMixer {
private:
T m_n1;
T m_n2;
};このように書くと、KiMixerは特定の型専用のクラスではなく、型をあとから指定できるクラスになります。
図:クラステンプレートは型に合わせて変化する設計図

この図が示していること
この図では、1つのクラステンプレートKiMixerが、int型用にもstring型用にも変化できることを表しています。
KiMixerとして使えば、Tがintになり、修行ポイントの合計を扱うクラスになります。
KiMixerとして使えば、Tがstringになり、技名の連結を扱うクラスになります。
同じクラスの設計図を使いながら、型に応じて別のクラスのように使えることが分かります。
今回作成するサンプルプログラム
今回のサンプルでは、KiMixerというクラステンプレートを作ります。
KiMixerは、2つの値を保存し、その2つを+でまとめるクラスです。
Tがintなら、2つの数値を足し合わせます。
Tがstringなら、2つの文字列を連結します。
| 型指定 | クラスのイメージ | addの結果 |
|---|---|---|
| KiMixer | int型の値を2つ保存するクラス | 数値の合計 |
| KiMixer | string型の値を2つ保存するクラス | 文字列の連結 |
今回のプログラムでは、テンプレートクラスの定義をヘッダーファイルに書きます。
通常のクラスでは、クラスの宣言を.hに書き、メンバ関数の定義を.cppに書く形をよく使います。
しかし、クラステンプレートでは、テンプレートの定義をコンパイラが使える状態にしておく必要があるため、基本的にヘッダー内に実装まで書きます。
このサンプルでも、KiMixerクラスはki_mixer.hにまとめて書きます。
KiMixerクラスの定義
プロジェクト/ファイル名: Chap6_03/ki_mixer.h
#ifndef _KI_MIXER_H_
#define _KI_MIXER_H_
// クラステンプレート
template<typename T>
class KiMixer {
private:
// 1つ目の修行データ
T m_n1;
// 2つ目の修行データ
T m_n2;
public:
// 2つの値を設定する
inline void set(const T n1, const T n2) {
m_n1 = n1;
m_n2 = n2;
}
// 2つの値をまとめた結果を返す
inline T add() const {
return m_n1 + m_n2;
}
};
#endif // _KI_MIXER_H_このヘッダーファイルでは、KiMixerというクラステンプレートを定義しています。
最初に注目したいのは、次の部分です。
template<typename T>
class KiMixer {templateは、このクラスの中でTという型を使うことを示しています。
そのため、クラス内のメンバ変数にもTを使えます。
T m_n1;
T m_n2;Tは、KiMixerを使うときに指定される型に置き換わります。
たとえば、KiMixerとして使うと、m_n1とm_n2はint型になります。
KiMixerとして使うと、m_n1とm_n2はstring型になります。
main.cppでクラステンプレートを使う
次に、main.cppでKiMixerを使います。
プロジェクト/ファイル名: Chap6_03/main.cpp
#include <iostream>
#include <string>
#include "ki_mixer.h"
using namespace std;
int main(int argc, char** argv) {
// int型を扱う修行データミキサー
KiMixer<int> powerMixer;
// string型を扱う技名ミキサー
KiMixer<string> techniqueMixer;
// 修行ポイントを設定する
powerMixer.set(1, 2);
// 技名を設定する
techniqueMixer.set(string("かめ"), string("はめ波"));
// それぞれの結果を表示する
cout << powerMixer.add() << endl;
cout << techniqueMixer.add() << endl;
// プログラムが正常に終わったことを示す
return 0;
}実行結果
3
かめはめ波このプログラムでは、同じKiMixerクラスを2種類の型で使っています。
KiMixer<int> powerMixer;
KiMixer<string> techniqueMixer;powerMixerは、Tにintを指定したKiMixerです。
そのため、m_n1とm_n2はint型として扱われます。
techniqueMixerは、Tにstringを指定したKiMixerです。
そのため、m_n1とm_n2はstring型として扱われます。
同じKiMixerというクラスの設計図から、int用とstring用の2つの使い方ができているわけです。
KiMixerのイメージ
KiMixerと書くと、Tがintに置き換わります。
つまり、次のクラステンプレートは、
template<typename T>
class KiMixer {
private:
T m_n1;
T m_n2;
public:
inline void set(const T n1, const T n2) {
m_n1 = n1;
m_n2 = n2;
}
inline T add() const {
return m_n1 + m_n2;
}
};Tをintとして見ると、次のようなクラスのイメージになります。
class KiMixer_int {
private:
int m_n1;
int m_n2;
public:
inline void set(const int n1, const int n2) {
m_n1 = n1;
m_n2 = n2;
}
inline int add() const {
return m_n1 + m_n2;
}
};実際にこの名前のクラスを書くわけではありませんが、考え方としてはこのようにTがintに置き換わります。
そのため、次の処理では、1と2がint型の値として保存されます。
powerMixer.set(1, 2);そして、addを呼び出すと、m_n1 + m_n2が計算されます。
cout << powerMixer.add() << endl;結果は3になります。
KiMixerのイメージ
KiMixerと書くと、Tがstringに置き換わります。
そのため、メンバ変数m_n1とm_n2はstring型として扱われます。
イメージとしては、次のようなクラスになります。
class KiMixer_string {
private:
string m_n1;
string m_n2;
public:
inline void set(const string n1, const string n2) {
m_n1 = n1;
m_n2 = n2;
}
inline string add() const {
return m_n1 + m_n2;
}
};この場合、addの中で行われるm_n1 + m_n2は、数値の加算ではありません。
string型の+なので、文字列の連結になります。
main.cppでは、次のように値を設定しています。
techniqueMixer.set(string("かめ"), string("はめ波"));そして、addを呼び出すと、2つの文字列が連結されます。
cout << techniqueMixer.add() << endl;結果は、かめはめ波になります。
同じaddという名前でも、Tがintなら数値の合計になり、Tがstringなら文字列の連結になります。
ここに、クラステンプレートの面白さがあります。
図:Tがクラス全体の型を決める

この図が示していること
この図では、KiMixerのTが、クラス全体の型を決めていることを表しています。
Tがintになると、メンバ変数も引数も戻り値もintとして扱われます。
Tがstringになると、メンバ変数も引数も戻り値もstringとして扱われます。
関数テンプレートでは関数だけが型に合わせて変化しましたが、クラステンプレートではクラス全体が型に合わせて変化します。
クラステンプレートはヘッダーに書く
このサンプルでは、ki_mixer.cppを作っていません。
KiMixerの定義は、ki_mixer.hにまとめて書いています。
これは、クラステンプレートではとても大切なポイントです。
通常のクラスでは、ヘッダーにクラス宣言を書き、cppファイルにメンバ関数の定義を書くことがよくあります。
しかし、テンプレートでは、Tにどの型が入るかが、使われる場所で決まります。
そのため、コンパイラがKiMixerやKiMixerを作るときに、テンプレートの中身まで見える必要があります。
もしテンプレートの実装がcppファイル側に分かれていて、使う場所から見えない状態になると、必要な型に対するコードを生成できないことがあります。
そのため、クラステンプレートでは、基本的にヘッダー内に実装まで書きます。
| 通常のクラス | クラステンプレート |
|---|---|
| 宣言を.h、定義を.cppに分けることが多い | ヘッダー内に実装まで書くことが多い |
| 型はあらかじめ決まっている | 型は使うときに決まる |
| cppで定義しても扱いやすい | コンパイラがテンプレートの中身を見られる必要がある |
クラステンプレートでは、ヘッダーが設計図であると同時に、型に応じたクラスを作るための材料にもなります。
ドラゴンボール風に言えば、KiMixerの設計図は、修行司令室からいつでも見える場所に置いておく必要があります。
int用の装置を作るときも、string用の装置を作るときも、その設計図が見えていなければ組み立てられないからです。
set関数の役割
KiMixerクラスには、set関数があります。
inline void set(const T n1, const T n2) {
m_n1 = n1;
m_n2 = n2;
}set関数は、2つの値をメンバ変数に保存するための関数です。
Tがintなら、int型の値を2つ受け取ります。
powerMixer.set(1, 2);Tがstringなら、string型の値を2つ受け取ります。
techniqueMixer.set(string("かめ"), string("はめ波"));set関数では、引数n1とn2を受け取り、それぞれm_n1とm_n2に代入しています。
m_n1 = n1;
m_n2 = n2;つまり、KiMixerは、まずsetで2つのデータを保存し、そのあとaddでその2つをまとめるクラスです。
修行世界でたとえるなら、setは修行装置に2つのエネルギーをセットする操作です。
int型なら修行ポイントをセットし、string型なら技名の前半と後半をセットします。
add関数の役割
add関数は、保存されている2つの値を+でまとめて返します。
inline T add() const {
return m_n1 + m_n2;
}戻り値の型はTです。
Tがintなら、addはintを返します。
Tがstringなら、addはstringを返します。
同じaddでも、Tに入る型によって意味が変わります。
| Tの型 | m_n1 | m_n2 | addの結果 |
|---|---|---|---|
| int | 1 | 2 | 3 |
| string | かめ | はめ波 | かめはめ波 |
このように、クラステンプレートでは、同じメンバ関数でも、型によって動き方が変わります。
ただし、これは+演算子がその型で使える場合に限ります。
intやstringでは+が使えるので問題ありません。
もしTに+が使えない型を指定した場合、addの中のm_n1 + m_n2でコンパイルエラーになります。
テンプレートはどんな型でも必ず動くわけではありません。
その型に対して、クラス内で使っている操作が有効である必要があります。
const修飾子の意味
add関数の後ろには、constが付いています。
inline T add() const {
return m_n1 + m_n2;
}このconstは、add関数がメンバ変数の状態を変更しないことを表します。
addは、m_n1とm_n2を使って結果を返しますが、m_n1やm_n2の値を書き換える必要はありません。
そのため、関数の後ろにconstを付けて、「この関数はインスタンスの状態を変えません」と示しています。
| 関数 | メンバ変数を書き換えるか | constを付けるか |
|---|---|---|
| set | m_n1とm_n2を書き換える | 付けない |
| add | m_n1とm_n2を読むだけ | 付ける |
setは値を設定する関数なので、メンバ変数を書き換えます。
そのため、setの後ろにはconstを付けません。
addは値を読むだけで、メンバ変数を書き換えません。
そのため、addの後ろにconstを付けるのが自然です。
ドラゴンボール風に言えば、setは修行装置にエネルギーを入れ替える操作です。
addは、今セットされているエネルギーを読み取って結果を表示するだけの操作です。
読み取るだけなら装置の状態は変わらないので、constを付けられるわけです。
inline修飾子の意味
今回のset関数とadd関数には、inlineが付いています。
inline void set(const T n1, const T n2) {
m_n1 = n1;
m_n2 = n2;
}inline T add() const {
return m_n1 + m_n2;
}
inline修飾子が付いた関数は、インライン関数と呼ばれます。
インライン関数は、コンパイル時に関数呼び出しの場所へ処理が展開されることを期待するための書き方です。
通常の関数では、関数を呼び出すときに、別の場所にある関数本体へ処理が移ります。
一方、インライン展開が行われると、関数の中身が呼び出し元へ埋め込まれるようなイメージになります。
小さくて短い関数では、関数呼び出しの手間が減り、処理が速くなることがあります。
ただし、inlineを付ければ必ず速くなるわけではありません。
実際にインライン展開するかどうかは、コンパイラが判断します。
また、長い処理にinlineを付けると、同じ処理が呼び出し元に何度も展開され、プログラムのサイズが大きくなることがあります。
| 関数の特徴 | inlineに向いているか |
|---|---|
| 短い処理 | 向いている |
| 何度も呼ばれる小さな関数 | 向いている場合がある |
| 長い処理 | 向いていない |
| 複雑な処理 | 効果が分かりにくい |
今回のsetやaddは短い処理なので、inlineの例として分かりやすい形になっています。
図:inline関数は呼び出し元に処理が埋め込まれるイメージ

この図が示していること
この図では、通常の関数呼び出しとinline関数の違いを表しています。
通常の関数では、呼び出し元から関数本体へ処理が移るイメージです。
inline関数では、関数の処理が呼び出し元に埋め込まれるようなイメージになります。
ただし、inlineを付ければ必ずそうなるわけではなく、最終的にはコンパイラの判断に任されます。
inline関数を使うときの注意
inlineは便利ですが、何でも付ければよいわけではありません。
短い関数なら、呼び出しの手間を減らせる可能性があります。
しかし、処理が長い関数にinlineを付けると、呼び出し元のあちこちに処理が展開され、生成されるプログラムのサイズが大きくなることがあります。
ドラゴンボール風にたとえるなら、小さな気弾ならすぐにその場で出せますが、巨大な必殺技を毎回その場に埋め込むと、修行場全体が重くなってしまうようなものです。
inlineは、小さくて分かりやすい処理に使うのが基本です。
今回のsetやaddは処理が短いため、inlineの例として扱いやすい関数です。
クラステンプレートとポリモーフィズム
クラステンプレートも、広い意味ではポリモーフィズムの一種として考えられます。
これまで学んだポリモーフィズムには、オーバーロードやオーバーライドがありました。
オーバーロードは、同じ関数名を引数の違いで使い分ける仕組みでした。
オーバーライドは、親クラスの関数を子クラスで作り直す仕組みでした。
クラステンプレートは、それらとは少し違い、型をパラメータとして受け取り、型に応じてクラスの形を変えます。
| 種類 | 内容 | 例 |
|---|---|---|
| オーバーロード | 引数の型や数で関数を使い分ける | add()とadd(int, int) |
| オーバーライド | 子クラスで親クラスの関数を作り直す | KameWarrior::shout() |
| クラステンプレート | 型を指定してクラスを作る | KiMixer、KiMixer |
クラステンプレートのように、型をパラメータとして使う多相性は、パラメータ多相と呼ばれることがあります。
難しい言葉に見えるかもしれませんが、考え方はシンプルです。
Tという型のパラメータを受け取り、Tにintを入れればint用、Tにstringを入れればstring用として使える、ということです。
ドラゴンボール風に言えば、同じ修行装置の設計図に対して、修行ポイント型、技名型、独自の戦士データ型など、どの型をセットするかで装置の姿が変わるようなものです。
クラステンプレートを使うメリット
クラステンプレートを使うと、型だけが違う似たクラスを何度も作らずに済みます。
たとえば、int型専用のIntMixer、string型専用のStringMixerを別々に作ることもできます。
しかし、それでは型が増えるたびにクラスが増えてしまいます。
KiMixerとして作っておけば、Tにintやstringを指定するだけで、それぞれの型に対応できます。
| テンプレートを使わない場合 | テンプレートを使う場合 |
|---|---|
| IntMixer、StringMixerなど型ごとにクラスを作る | KiMixerを1つ作る |
| 型が増えるとクラスも増える | 型を指定するだけで対応できる |
| 共通処理の修正箇所が増えやすい | 共通処理を1か所で管理しやすい |
| コードが重複しやすい | 汎用性が高くなる |
同じ構造を持つクラスを型ごとに作るのではなく、型を差し替えられるクラスとして作る。
これが、クラステンプレートの大きなメリットです。
クラステンプレートで押さえたい感覚
クラステンプレートでは、クラスの中に出てくる型をTとしてまとめます。
今回のKiMixerでは、メンバ変数、set関数の引数、add関数の戻り値にTを使いました。
T m_n1;
T m_n2;
inline void set(const T n1, const T n2)
inline T add() constこれにより、KiMixerならTがintになり、KiMixerならTがstringになります。
クラステンプレートを使うときは、「このクラスは型が違っても同じ構造で使えるか」を考えると理解しやすくなります。
今回のKiMixerは、2つの値を保存し、+でまとめて返すという構造を持っています。
この構造は、intでもstringでも共通して使えます。
だからこそ、クラステンプレートに向いています。
一方で、型によってまったく違う処理が必要な場合は、無理にクラステンプレートにまとめると分かりにくくなることもあります。
テンプレートは、共通化できる構造があるときに使うと効果的です。
C++のクラステンプレートは、関数テンプレートより少し大きな考え方になります。
関数だけでなく、クラス全体を型に合わせて変化させるからです。
ドラゴンボール風に言えば、KiMixerは万能修行装置の設計図です。
Tにintを入れれば修行ポイントを合計する装置になり、Tにstringを入れれば技名を連結する装置になります。
1つの設計図から、型に応じて複数のクラスのように使える。
それが、クラステンプレートの大きな力です。
