web-dev-qa-db-ja.com

C ++テンプレート関数定義を.CPPファイルに格納する

ヘッダーにインラインではなくCPPファイルに格納したいテンプレートコードがあります。どのテンプレートタイプが使用されるのかを知っている限り、これは可能です。例えば:

.hファイル

class foo
{
public:
    template <typename T>
    void do(const T& t);
};

.ppファイル

template <typename T>
void foo::do(const T& t)
{
    // Do something with t
}

template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);

最後の2行に注意してください - foo :: doテンプレート関数はintsとstd :: stringsでのみ使用されるので、これらの定義はアプリがリンクすることを意味します。

私の質問は - これは厄介なハックなのか、それとも他のコンパイラやリンカとの連携でしょうか?私は現在VS2008でこのコードを使っているだけですが、他の環境に移植したいと思うでしょう。

441
Rob

あなたが説明する問題は、ヘッダでテンプレートを定義することによって、またはあなたが上で説明したアプローチによって解決することができます。

C++ FAQ Lite から、次の点を読むことをお勧めします。

彼らはこれらの(そして他の)テンプレートの問題について多くの詳細に入ります。

195
Aaron N. Tubbs

このページの他の人たちのために(私がそうであったように)明示的なテンプレートの特殊化(あるいは少なくともVS2008)のための正しい構文が何であるか疑問に思う、それは以下の...

あなたの.hファイルに...

template<typename T>
class foo
{
public:
    void bar(const T &t);
};

そしてあなたの.cppファイルに

template <class T>
void foo<T>::bar(const T &t)
{ }

// Explicit template instantiation
template class foo<int>;
103
namespace sid

このコードは整形式です。インスタンス化の時点でテンプレートの定義が表示されていることに注意するだけで済みます。規格を引用すると、§14.7.2.4:

クラステンプレートのエクスポートされていない関数テンプレート、エクスポートされていないメンバ関数テンプレート、またはエクスポートされていないメンバ関数または静的データメンバの定義は、明示的にインスタンス化されているすべての変換単位に存在します。

19
Konrad Rudolph

これはテンプレートがサポートされているところならどこでもうまく動作するはずです。明示的なテンプレートのインスタンス化はC++標準の一部です。

11
moonshadow

あなたの例は正しいですが、あまり移植性がありません。 (@ namespace-sidで指摘されているように)使用できるややきれいな構文もあります。

テンプレート化されたクラスが、共有される予定のあるライブラリの一部であるとします。テンプレートクラスの他のバージョンをコンパイルする必要がありますか?ライブラリのメンテナは、このクラスのテンプレート化された使用法すべてを予想することになっていますか?

代わりのアプローチはあなたが持っているものに対するわずかな変化です:テンプレートの実装/インスタンス化ファイルである3番目のファイルを追加します。

foo.hファイル

// Standard header file guards omitted

template <typename T>
class foo
{
public:
    void bar(const T& t);
};

foo.cppファイル

// Always include your headers
#include "foo.h"

template <typename T>
void foo::bar(const T& t)
{
    // Do something with t
}

foo-impl.cppファイル

// Yes, we include the .cpp file
#include "foo.cpp"
template class foo<int>;

注意点の1つは、後者をコンパイルしても何も起こらないので、foo-impl.cppの代わりにfoo.cppをコンパイルするようコンパイラーに指示する必要があることです。

もちろん、3番目のファイルに複数の実装を含めることも、使用する種類ごとに複数の実装ファイルを含めることもできます。

テンプレート化されたクラスを他の用途に共有するとき、これによりはるかに高い柔軟性が可能になります。

各翻訳単位で同じヘッダーファイルを再コンパイルしていないため、この設定では再利用クラスのコンパイル時間も短縮されます。

8

これは明らかに厄介なハックではありませんが、与えられたテンプレートで使用したいすべてのクラス/タイプに対してそれをしなければならないということ(明示的なテンプレート特殊化)を知っておいてください。テンプレートのインスタンス化を要求する多くの型の場合、.cppファイルにたくさんの行がある可能性があります。この問題を解決するには、使用するすべてのプロジェクトにTemplateClassInst.cppを設定して、どのタイプをインスタンス化するかを細かく制御できます。あなたがODRを壊すことになるかもしれないので:明らかにこの解決策は(別名銀の弾丸)完全ではないでしょう:)。

5
Red XIII

最新の規格には、この問題を軽減するのに役立つキーワード(export)がありますが、私が知っているどのコンパイラにも実装されていません。

これについては FAQ-lite をご覧ください。

4
Ben Collins

はい、それは標準的なやり方です 特化 明示的インスタンス化あなたが述べたように、あなたは他の型でこのテンプレートをインスタンス化することはできません。

編集:コメントに基づいて修正。

3
Lou Franco

これはテンプレート関数を定義するための標準的な方法です。テンプレートを定義する方法は3つあります。それともおそらく4。長所と短所があります。

  1. クラス定義で定義してください。クラス定義はあくまで参考用であり、読みやすくするべきだと思うので、これはまったく好きではありません。しかし、クラス内でテンプレートを定義することは、外部よりもはるかに簡単です。そして、すべてのテンプレート宣言が同じレベルの複雑さにあるわけではありません。このメソッドはテンプレートを真のテンプレートにもします。

  2. テンプレートを同じヘッダーの中で、クラスの外側で定義します。ほとんどの場合、これは私にとって好ましい方法です。それはあなたのクラス定義をきちんと保ちます、テンプレートは本当のテンプレートのままです。しかしながら、それは完全なテンプレート命名を必要としますが、これは難しいかもしれません。また、あなたのコードは誰にでも利用可能です。しかし、コードをインラインにする必要がある場合は、これが唯一の方法です。クラス定義の最後に.INLファイルを作成してこれを達成することもできます。

  3. Main.CPPにheader.hとimplementation.CPPを含めます。私はそれがそのようにしたと思います。事前のインスタンス化を準備する必要はなく、実際のテンプレートのように動作します。私が抱えている問題は、それが自然ではないということです。通常はソースファイルをインクルードしませんし、インクルードする予定です。ソースファイルをインクルードしてから、テンプレート関数をインライン化できると思います。

  4. この最後の方法はポストされた方法で、番号3のようにソースファイルでテンプレートを定義します。しかし、ソースファイルをインクルードする代わりに、テンプレートを必要なものに事前インスタンス化します。私はこの方法に問題はありません、そしてそれは時々役に立ちます。大きなコードが1つあります。インライン化してもメリットはありません。CPPファイルに入れるだけです。そして、私たちが共通の具体化を知っていて、それらを事前に定義することができるなら。これで、基本的に同じことを5回、10回書くのを防ぐことができます。この方法には、コードを独自のものにしておくという利点があります。しかし、私は、CPPファイルに、定期的に使用される小さな機能を入れることをお勧めしません。これはあなたのライブラリのパフォーマンスを低下させるので。

注意してください、私は肥大化したobjファイルの影響を認識していません。

3

1つの例を見てみましょう。何らかの理由でテンプレートクラスが必要だとしましょう。

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

このコードをVisual Studioでコンパイルすると、そのまま使用できます。 gccはリンカーエラーを生成します(同じヘッダーファイルが複数の.cppファイルから使用されている場合):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

実装を.cppファイルに移動することは可能ですが、次のようにクラスを宣言する必要があります-

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

そして、.cppは次のようになります。

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

ヘッダーファイルに最後の2行がない場合-gccは正常に機能しますが、Visual Studioはエラーを生成します。

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

.dllエクスポート経由で関数を公開する場合、テンプレートクラスの構文はオプションですが、これはWindowsプラットフォームにのみ適用されます。したがって、test_template.hは次のようになります。

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

前の例の.cppファイルを使用します。

ただし、これはリンカーに頭痛の種を与えるため、.dll関数をエクスポートしない場合は前の例を使用することをお勧めします。

1
TarmoPikaro

あなたが与えた例には何の問題もありません。しかし、関数定義をcppファイルに格納するのは効率的ではないと私は思う。関数の宣言と定義を分ける必要性だけを理解しています。

明示的なクラスのインスタンス化と組み合わせて使用​​すると、Boost Concept Check Library(BCCL)を使用してcppファイルにテンプレート関数コードを生成できます。

0
Benoît

更新の時間です!インライン(.inl、またはおそらく他の)ファイルを作成し、その中のすべての定義を単純にコピーします。必ず各関数の上にテンプレートを追加してください(template <typename T, ...>)。インラインファイルにヘッダーファイルを含める代わりに、今度はその逆を行います。インラインファイルクラスの宣言の後に含めます(#include "file.inl")。

誰もこれを述べていないのか、私は本当に知りません。差し迫った欠点はありません。

0
Didii