mogmo .NET

C#/XAML/VB6たまにC++についてメモ程度に書いていく。あくまで自分用。責任は取れません。

C++ | テンプレートの実装はヘッダファイルに書かなければならない

f:id:mogmo811:20200402170734j:plain

こんにちは。もぐもです。
久しぶりにC++を触っています。

さて,C++MFCアプリケーションを実装している際に,以下のようなエラーが出て四苦八苦しました。

LNK2019 未解決の外部シンボル "public: static int __cdecl Calc::Add(int,int)" (?Add@?$Calc@H@@SAHHH@Z) が関数 "private: void __thiscall CMFCApplication1Dlg::Adding(void)" (?Adding@CMFCApplication1Dlg@@AAEXXZ) で参照されました。

結論から言うと

原因を調べた結果,分割コンパイルによる実体の不在のためにおこるリンクエラーのようです。
対策は,テンプレートクラスや関数の実装をヘッダファイルで行うことでコンパイルが通ります。

原因について

サンプルコード

エラーの出るサンプルコードを記載します。
プロジェクトはMFCアプリケーションで作成しました。
MFCApplication1Dlgクラス の Adding関数 の中で Calcクラス の Add関数 を呼ぶコードになっている。

/* Calc.h */

#pragma once
template<class T>
class Calc
{
public:
    static int Add(T a, T b);
};
/* Calc.cpp */

#include "pch.h"
#include "Calc.h"

template<class T>
inline int Calc<T>::Add(T a, T b)
{
    return a + b;
}
/* MFCApplication1Dlg.cpp */

#include "Calc.h"

// ----- 省略 -----

void CMFCApplication1Dlg::Adding()
{
	int sum = Calc<int>::Add(10, 20);
}

ビルドするとどうなるか

ビルド出力は以下のようになった。

1>------ すべてのリビルド開始: プロジェクト:MFCApplication1, 構成: Debug Win32 ------
1>pch.cpp
1>Calc.cpp
1>MFCApplication1.cpp
1>MFCApplication1Dlg.cpp
1>コードを生成中...
1>MFCApplication1Dlg.obj : error LNK2019: 未解決の外部シンボル "public: static int __cdecl Calc::Add(int,int)" (?Add@?$Calc@H@@SAHHH@Z) が関数 "private: void __thiscall CMFCApplication1Dlg::Adding(void)" (?Adding@CMFCApplication1Dlg@@AAEXXZ) で参照されました。
1>C:\Users\***\repos\MFCApplication1\Debug\MFCApplication1.exe : fatal error LNK1120: 1 件の未解決の外部参照
1>プロジェクト "MFCApplication1.vcxproj" のビルドが終了しました -- 失敗。
========== すべてリビルド: 0 正常終了、1 失敗、0 スキップ ==========

Calc.cppコンパイル時,Add関数を使っている箇所が見当たらない→Add関数のテンプレートの実体化が行われません。
もっと言えば,今回のコードの場合がCalc自体がテンプレートクラスなのですが,Calcクラスを使用している箇所が見当たらないため,クラスの実体化も行われません。
結果,実体化していない状態でCalc.objファイルが生成されます。

次に,MFCApplication1Dlg.cppのコンパイルです。
MFCApplication1Dlg.cppではAdding関数でCalcクラスのAdd関数を使用していますが,Calc.hに宣言されているので実体の有無にかかわらずコンパイルが通り,MFCApplication1Dlg.objファイルが生成されます。

さて,これでコードを生成しようとすると,MFCApplication1Dlg.objちゃんはCalcクラスのAdd関数をCalc.objから探し出そうとしますが,Calc.objは分割コンパイル時に実体化が行われていないのでint型のAdd関数は存在しません。
よって,未解決の外部シンボルエラーが発生するという流れでビルドエラーになります。

正しい実装方法

インライン関数でヘッダファイルに実装します。
Calc.cppは空でOKです。

/* Calc.h */

#pragma once
template<class T>
class Calc
{
public:
    static int Add(T a, T b);
};

template<class T>
inline int Calc<T>::Add(T a, T b)
{
    return 0;
}

参考

以下ブログを参考にしました。
テンプレートの実装をヘッダに書かなければならない理由 - (void*)Pないと

せっかくなので自分の言葉で書いてみました。
間違い等あれば連絡ください。

ではまた。
もぐも(`・ω・´)🍙