はじめに
C++のテンプレートについて色々調べた時のメモです.
テンプレートとは、ざっくり言うと型を抽象化した関数やクラスのことです.
テンプレートの種類 (4つ)
1. 関数テンプレート
template <typename T> void hoge(T a) {};
・型推論: hoge(1);
, hoge(2.5);
・明示的な型指定: hoge<double>(2.2);
・"typename"は"class"でも可.
2. クラステンプレート
template <typename T> class MyClass {};
・明示的な型指定: MyClass<int> myclass
;
・関数テンプレートと違い、型推論は不可能.
・structに対しても使える.
3. メンバテンプレート
個別のメンバ関数のみテンプレート化するもの.
class MyClass {
int _a;
public:
Foo(int a) : _a(a) {}
template <typename T>
void get(T& b) const
{
b = _a;
}
};
4. エイリアステンプレート
テンプレートの別名を定義するもの. (C++11)
template <typename T, typename Allocator>
class Stack;
template <typename T>
using MyStack = Stack<T, MyAllocator<T>>;
MyStack<int> s(n);
Stackテンプレートの別名としてMyStackを作っている.
その際AllocatorをMyAllocatorテンプレートで指定している.
(この例では単なる別名ではなく、エイリアスを作る際に1つのテンプレート引数Allocatorを固定している.)
Note: usingはC++11で追加された、typedefのかわり.
using 新しい型 = 元になる型 と書ける.
基本的な書き方
クラス外部にメソッドを書くとき
template <typename T> void MyClass<T>::method() {};
・MyClassはクラステンプレートであって、
クラスではないためMyClass::
ではなくMyClass<T>::
とする.
・ただし、::以降は<T>
指定は不要.(コピーコンストラクタなど)
書いても良いが、自動的に挿入されるため.
template <typename T> MyClass<T>::MyClass(const MyClass& other) {};
[メモ]
・実装を含めたすべての情報が必要となるため、宣言と実装はすべてヘッダファイルに書く必要がある.
・メンバ関数を宣言と同時に定義すると、通常のクラス同様インライン化しようとする.
テンプレートに渡せる型
テンプレートに渡せる型に制限はないが、
記述した処理を適用できなければコンパイルエラーとなる.
(string型に対して算術演算はできないなど.)
-> ダックタイピング?
特徴
注意すべき挙動
- 型ごとに独立した関数/クラスがコンパイル時に作られる.
- 「テンプレートのインスタンス化」と言う
- たくさんインスタンス化するほどファイルサイズは増加する
- そのぶん動作が高速になる
デフォルト引数
デフォルトテンプレート引数をとることができる.
template <typename T = char> class MyClass
のようにする.
ただし、クラステンプレートで使う時は<>を省略できず、`MyClass<> myclass;`のようになる.
また、より先に出てきたテンプレート引数を使って
template <typename A, typename B = A>
や、
template <typename A, typename B = Array<A> >
とも書ける
最後の<A> >
の部分には必ず半角スペースが必要.
スペースがないとシフト演算子>>
とみなされコンパイルエラーとなる.
-> 最近のコンパイラなら大丈夫?
デフォルト引数に整数値を指定する
template <class T, int N = 10>
class Stack {
}
テンプレート引数にポインタを与える
template <class T, class Object,
T (Object::*real_getter)()const,
T (Object::*real_setter)()(const T&)>
setter/getterで使われるoperatorをオーバーロードして、
property機能を実現できる.
(このテンプレート型の変数へのアクセスが指定したsetter/getterに置き換えられる)
明示的な実体化
関数テンプレートを呼ばずにテンプレートを実体化する.
使いドコロはあまりないかも.
// テンプレート関数の実体化
template int hoge(int a);
// テンプレートクラスの実体化
template class MyClass<int>;
// メンバ関数だけ実体化
template int MyClass<int>::foo(const int&);
例えば、テンプレートの実装を実装ファイル(cpp)に分けておき、
実装ファイル内に上記の実体化コードを書いておく.
すると、その型でのみ実体化できるテンプレートができる.
コンストラクタテンプレート
MyClass<int>
とMyClass<long>
はまったく異なるクラスなので、通常は暗黙の型変換は不可能.
これを解消するにはコンストラクタテンプレートを使う.
template <class T>
class MyClass {
template<class> friend class MyClass;
private:
T _a;
T _b;
public:
// コンストラクタテンプレート
template <class U>
MyClass(const MyClass<U>& r) : _a(r._a), _b(r._b) {}
};
テンプレートを利用したコンストラクタを書くことにより、
型の違うMyClass<ing>
からMyClass<Long>
への型変換が可能になる.
その他
ポリモーフィズムを利用した関数との違い
共通な親クラスを使えば複数のクラスに対応した関数を作ることができる.
その場合はテンプレートと違い、関数は1つだけ作られる.
ただし、処理の異なる部分はどんなに単純でも仮想化(virtual修飾子)する必要があり、
インライン化も不可能.
それゆえにテンプレートと比べると動作は低速となる.
typeid
typeid(T)
で型情報を取得.
Tはクラス名でも、インスタンスでもOK(のはず)
typeid(T).name
で型の名前を取得
参考
- 粂井康孝(2009)『猫でもわかるC++プログラミング』 SBクリエイティブ.
- ロベール(2007)『ロベールのC++入門講座』 毎日コミュニケーションズ.
- επιστημη・高橋晶 (2014)『C++テンプレートテクニック 第2版』 SBクリエイティブ.