📝

[C++]テンプレートの実装はヘッダ側に書いた方がよい

2023/04/21に公開

C++のテンプレート周りでハマったので備忘録として記事に残す。

以下のようなファイル構成でテンプレート関数が実装されたソースをビルドするとリンクエラーになってしまう。

common.h
template <typename T>
T Sum(T a, T b);
common.cpp
#include "common.h"
template <typename T>
T Sum(T a, T b)
{
  return a + b;
}
main.cpp
#include "common.h"
int main()
{
  Sum(1,2);
  return 0;
}

リンクエラー↓

main.cpp:(.text+0x1e): undefined reference to `int Sum<int>(int, int)'
collect2.exe: error: ld returned 1 exit status

ビルド時にコンパイルまでは通るのでint型のSum関数の型は見つけることができているようだ。
しかしSum関数の中身はcppに定義されているので、リンク時に定義が見つからずエラーになってしまう模様。

ビルド時にエラーになるまでのおおまかな流れをまとめると以下のようになる。

  1. コンパイラがmain.cppのコンパイル
    ->テンプレート関数をint型で使用していてcommon.hに宣言があるのでmain.cppのコンパイルは通る。
  2. コンパイラがcommon.cppのコンパイル
    ->テンプレート関数の定義はあるがcommon.cpp内ではどこでも使われていないのでなにも実体化されない。
  3. リンカーがリンク
    ->リンカがint型のSum関数を探しにいくが実体が無いためリンクエラーになる。

ではどうやって解決すればよいかというと方法は2つある。

解決法①:明示的インスタンス化

以下のように明示的インスタンス化というやり方を使い使用される型のインスタンスを書いてあげる。

common.cpp
#include "common.h"
template <typename T>
T Sum(T a, T b)
{
  return a + b;
}

// 明示的インスタンス化
template int Sum(int a, int b);
template short Sum(short a, short b);

明示的インスタンス化をすることでコンパイラはこれらの型の定義が必要ということを認識してくれて、コンパイル時に必要なテンプレート関数の実体化が行われる。
それによりリンクも通せる。

解決法②:ヘッダに実装を書く

明示的インスタンス化の方法でもよいが、使用する型の種類が多いとそれだけ明示的インスタンス化の数も増えてしまう。
それが嫌なら実装自体をヘッダに書いてしまうという方法もある。

common.h
template <typename T>
T Sum(T a, T b)
{
  return a + b;
}

こうすることでmain.cppのコンパイル時点でコンパイラがテンプレート関数の中身まで認識して実体化もしてくれるのでリンクエラーにならない。

実装を隠蔽したい等の特段な理由がない限りはヘッダに実装を書くのがメンテも楽だし個人的にはおすすめ。

Discussion