はじめに
たとえばこういうクラステンプレートのコードがあるとします。
TestClass.h
#pragma once
#include <iostream>
template <typename T>
class TestClass
{
public:
TestClass(T num) : Num(num) {}
public:
T SayNum() const
{
std::cout << "Num: " << Num << std::endl;
return Num;
}
private:
T Num;
};
main.cpp
#include "TestClass.h"
int main(void)
{
TestClass<int> intTest(10);
intTest.SayNum();
return 0;
}
出力
Num: 10
正常に動作しました。
これをいつものように.hに.cppにファイル分割してみようと思いますが、
TestClass.h
#pragma once
template <typename T>
class TestClass
{
public:
TestClass(T num);
public:
T SayNum() const;
private:
T Num;
};
TestClass.cpp
#include <iostream>
#include "TestClass.h"
template <typename T>
TestClass<T>::TestClass(T num) : Num(num) {}
template <typename T>
T TestClass<T>::SayNum() const
{
std::cout << "Num: " << Num << std::endl;
return Num;
}
main.cpp
#include "TestClass.h"
int main(void)
{
TestClass<int> intTest(10);
intTest.SayNum();
return 0;
}
どうなるのか、実行してみましょう。
どのファイルにもエラーが出ていませんが、未解決の外部シンボルエラーが出ました。
テンプレートの特性上、コンパイル時にインスタンス化されます。
TestClassが使用されるときにSayNumの実装が必要ですが、cppに実装部があるだけではリンク時に解決できないためリンクエラーが発生してしまうということですね。
更に詳細なことは割愛するとして、常に.hの中に書くわけにはいかないですよね。
プロジェクトが巨大化すればするほどコードの可読性が下がりかねません。
ではどのように分割すればよいのか、
私が普段行っているファイル分割の方法を説明します。
ファイル分割方法
TestClass.h
#pragma once
template <typename T>
class TestClass
{
public:
TestClass(T num);
public:
T SayNum() const;
private:
T Num;
};
#include "TestClass.tpp" // TestClass.hppでも可
TestClass.tpp (TestClass.hpp)
#include <iostream
template<typename T>
TestClass<T>::TestClass(T num) : Num(num)
{
}
template <typename T>
T TestClass<T>::SayNum() const
{
std::cout << "Num: " << Num << std::endl;
return Num;
}
main.cpp
#include "TestClass.h"
int main(void)
{
TestClass<int> intTest(10);
intTest.SayNum();
return 0;
}
.hの最後に実装部分をincludeします。
この方法が一番わかりやすくて自分はよく使います。
注意点は.cppとした後に.hppや.tppとすると、このようにC/C++コンパイラとなっています。
なのでここを、
さいごに
明示的に.cppの後ろにtemplate class TestClass<int>;
とやる方法などがありますが、
汎用性が下がり、テンプレートじゃなくていいじゃんとなるのでおススメしません。