C++テンプレートの実装設計:.cpp
と.hh
(または.hpp
)ファイルに分ける具体的手法
C++テンプレートは強力なメタプログラミング手法ですが、その定義と実装の分離には独特の注意点が存在します。特に.cpp
ファイルと.hh
(または.hpp
)に分離して設計しようとすると、通常のクラスや関数とは異なるルールが必要です。
この記事では、中級者〜上級者向けにC++テンプレートの設計を.hh
と.cpp
ファイルに分離する際の正しいアプローチを、コード例、設計方針、トラブルシューティングを含めて詳解します。
1. C++テンプレートの基本構造と課題
テンプレート関数の基本形
template <typename T> T add(T a, T b) { return a + b; }
これはヘッダーファイル(.hh
や.hpp
)に書かれている限り問題ありません。しかし、この定義を.cppファイルに書いてしまうと、テンプレートのインスタンス化に失敗します。
なぜ.cpp
分離が難しいのか?
C++のテンプレートはコンパイル時にインスタンス化されるため、関数やクラスの定義が見える(=テンプレート定義がヘッダーにある)必要があります。
2. 実装設計の選択肢
設計方針 | 特徴 |
---|---|
ヘッダーオンリー | すべての実装を.hh に書く。最も一般的。 |
明示的インスタンス化 | .cpp に特定型だけ定義し、ヘッダーにextern template を書く |
ヘッダー+実装分離(.tcc) | テンプレート定義を.tcc などの実装専用ヘッダーに分けてinclude |
3. 最も一般的な方法:.tcc
方式(実装分離)
ファイル構成
MyTemplate.hh ← クラス定義、関数宣言 MyTemplate.tcc ← テンプレート関数実装 main.cpp ← 利用コード
MyTemplate.hh
#ifndef MY_TEMPLATE_HH #define MY_TEMPLATE_HH template <typename T> class MyTemplate { public: MyTemplate(T val); void print() const; private: T value; }; // 実装ファイルをインクルード #include "MyTemplate.tcc" #endif
MyTemplate.tcc
#include <iostream> template <typename T> MyTemplate<T>::MyTemplate(T val) : value(val) {} template <typename T> void MyTemplate<T>::print() const { std::cout << "Value: " << value << std::endl; }
main.cpp
#include "MyTemplate.hh" int main() { MyTemplate<int> obj(10); obj.print(); return 0; }
ポイント: -
.tcc
ファイルは.cpp
ではなく.hh
からインクルードする -.cpp
にリンクされるのはあくまでインスタンス化された型だけ
4. 明示的インスタンス化を使った.cpp
分離
特定の型に限定したい場合、以下のような方法も有効です。
MyTemplate.hh
#ifndef MY_TEMPLATE_HH #define MY_TEMPLATE_HH template <typename T> class MyTemplate { public: MyTemplate(T val); void print() const; private: T value; }; // 明示的インスタンス化の宣言 extern template class MyTemplate<int>; #endif
MyTemplate.cpp
#include "MyTemplate.hh" #include <iostream> template <typename T> MyTemplate<T>::MyTemplate(T val) : value(val) {} template <typename T> void MyTemplate<T>::print() const { std::cout << "Value: " << value << std::endl; } // 明示的インスタンス化 template class MyTemplate<int>;
main.cpp
#include "MyTemplate.hh" int main() { MyTemplate<int> obj(42); obj.print(); }
注意:この方法では、
MyTemplate<double>
などを使うとリンクエラーになる
5. 設計指針とベストプラクティス
ヘッダオンリー vs 明示的インスタンス化
項目 | ヘッダーオンリー | 明示的インスタンス化 |
---|---|---|
コンパイル時間 | 長くなりがち | 分離できるので短縮可能 |
可読性・保守性 | 実装が混在して見づらくなる | .cpp にまとめて記述できて整理しやすい |
テンプレートの汎用性 | 高い(どの型でも使える) | 限定的(指定型のみ) |
推奨戦略
- ライブラリで型汎用性を重視するなら:
.tcc
方式 - アプリケーションで使う型が限定されているなら:明示的インスタンス化方式
- 社内コードでビルド速度が問題になるなら:
.cpp
でインスタンス化してテンプレート汎用性を抑えるのも一案
6. まとめ
C++テンプレートの実装は柔軟性が高い反面、コンパイル時インスタンス化の特性ゆえに構造が複雑化しやすい点に注意が必要です。
- 基本的には
.hh + .tcc
で設計し、必要に応じて.cpp
への明示的インスタンス化を使い分ける - ヘッダーファイルには「実装」も必要だということを忘れない
- 汎用性とビルド時間、保守性のトレードオフを意識して設計すること
参考リンク
参考書籍
リンク
リンク
リンク