C++関数テンプレートと分離コンパイルモード


1.分離コンパイルモード1つのプログラム(プロジェクト)は複数のソースファイルによって共通に実現され、各ソースファイルは別々にコンパイルしてターゲットファイルを生成し、最後にすべてのターゲットファイルを接続して単一の実行可能ファイルを形成するプロセスを分離コンパイルモードと呼ぶ.
2.関数テンプレートを使用したリンクエラー
C++プログラミングでは、あるソースファイルで関数を定義し、別のソースファイルでその関数を使用するのが一般的です.ただし、関数テンプレートを定義して呼び出すときにもこの方法を使用すると、コンパイルエラーが発生します.
次のプログラムは3つのファイルから構成されています:func.h関数テンプレートの説明に用いるfunc.cppは関数テンプレートを定義するために使用されます.main.cppはfuncを含む.hヘッダファイルを呼び出し、対応する関数テンプレートを呼び出します.
/***func.h***/
template<class T> void func(const T&);
/***end func.h***/
/***func.cpp***/
#include 

#include "func.h"
template<class T> void func(const T& t)
{
     
 std::cout<<t<<std::endl;
}
/***end func.cpp***/
/***main.cpp***/
#include 
#include "func.h"

int main()
{
     
 func(3);
}
/***end main.cpp***/

これは構造が非常に明確なプログラムですが、コンパイルできません.VS 2017でのエラーメッセージは、
error LNK2019:           "void __cdecl func< int>(int const &)" (??$func@H@@YAXABH@Z)

原因は分離コンパイルモードに現れます.分離コンパイルモードでfunc.cppはfuncとしてターゲットファイルを生成する.objはfunc.cppファイルでは、関数テンプレート呼び出しは発生しないので、関数テンプレートfuncをテンプレート関数func、すなわちfuncにインスタンス化することはない.objではテンプレート関数funcに関する実装コードが見つかりません.
ソースファイルmain.cppでは,関数テンプレートが呼び出されるが,テンプレートコードがないため実例化できない.つまりmain.objにもテンプレート関数funcの実装コードが見つからない.これにより、リンク時にfuncで定義されていないエラーが発生します
3.解決策
3.1関数テンプレートの定義をヘッダファイルに配置する
簡単な解決策は、関数テンプレートfuncの定義をヘッダファイルfuncに書き込むことである.h中.これにより,このヘッダファイルが含まれていれば,関数テンプレートのコードが含まれ,関数呼び出しが発生すると,関数テンプレートコードに基づいて実例化できる.この方法はboost、dlibなど、一般的なライブラリで使用されます.
この方法は簡単で実行できるが、以下の不足がある.
  • 関数テンプレートの定義はヘッダファイルに書き込まれ、関数テンプレートの実装の詳細が明らかになった.
  • は、分離コンパイルモードのルールに合致しない.分離コンパイルモードは、関数プロトタイプがヘッダファイルに配置され、ソースファイルに配置されることを定義することを要求するためである.

  • 注:これは、複数のターゲットファイルに同じ関数テンプレートがインスタンス化されたテンプレート関数エンティティが存在する場合、リンク時に関数の再定義のエラーは報告されません.これは、コンパイラがインスタンス化された重複するテンプレート関数エンティティを最適化し、コードエンティティを1つだけ保持するため、通常の関数とは異なります.異なるソースファイルに関数テンプレートエンティティが1つ保持されている場合、コードが冗長になります.実際には、これもコード冗長の解決策です.
    3.2依然として分離コンパイルモードを採用する
    関数テンプレートをインスタンス化するときに、対応するテンプレート関数のコードを見つける方法はありますか?可能な解決策は、キーワードexportを使用することです.つまりfuncではcppで関数テンプレートを定義するときは、関数テンプレートヘッダを次のように書きます.
    export template<class T> void func(const T& t);
    

    この目的は、この関数テンプレートが他のソースファイルでインスタンス化される可能性があることをコンパイラに伝えることです.これはプログラマーにとって最も負担の少ない解決策ですが、現在、ほとんどのコンパイラではVC++やGNU C++などのキーワードexportはサポートされていません.
    3.3インスタンス化の表示(推奨)
    インスタンスの表示は外部インスタンスとも呼ばれます.関数呼び出しが発生しないときに関数テンプレートをインスタンス化するか、クラステンプレートを使用しないときにクラステンプレートをインスタンス化することをテンプレート表示インスタンス化と呼ぶ.
    上の問題はmainです.OBjとfunc.objにテンプレート関数funcの実装コードが見つからない場合func.cppでは、関数テンプレートfuncの表示をテンプレート関数funcにインスタンス化する.
    template void func<int>(const int&); //         
    

    これでfunc.cppはテンプレート関数funcのインスタンス化コードを生成し、コンパイル後に関数のバイナリコードを生成し、他のソースファイルへのリンクを提供し、プログラムは正常に動作する.クラステンプレートのメンバー関数の実装定義がソースファイルにある場合、テンプレートクラスのオブジェクトからメンバー関数を呼び出すと、関数定義が見つからないエラーも発生します.同様の方法で解決できます.
    ref: https://www.jb51.net/article/193095.htm
    テンプレート分離コンパイルでは、使用するすべてのタイプのテンプレートをcppで明示的にインスタンス化することを推奨します.このcppファイルをコンパイルすると、対応するタイプのテンプレート関数が最終的に対応するobjにシンボルリンクを生成し、他のモジュールで呼び出すことができます.リンクできない場合があります.