web-dev-qa-db-ja.com

テンプレートはなぜヘッダーファイルにしか実装できないのですか?

C++標準ライブラリからの引用:チュートリアルとハンドブック

現時点でテンプレートを使用する唯一の移植可能な方法は、インライン関数を使用してテンプレートをヘッダーファイルに実装することです。

どうしてこれなの?

(明確化:ヘッダファイルは only ポータブルソリューションではありませんが、最も便利なポータブルソリューションです。)

1564
MainID

実装をヘッダファイルに入れることはnot必要です、この答えの終わりに別の解決策を見てください。

とにかく、あなたのコードが失敗する理由は、テンプレートをインスタンス化するとき、コンパイラは与えられたテンプレート引数で新しいクラスを作成することです。例えば:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

この行を読むとき、コンパイラは新しいクラスを作成します(それをFooIntと呼びましょう)。これは以下と同等です。

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

したがって、コンパイラは、テンプレート引数(この場合はint)を使用してメソッドをインスタンス化するために、メソッドの実装にアクセスする必要があります。これらの実装がヘッダーに含まれていないと、アクセスできなくなるため、コンパイラはテンプレートをインスタンス化できません。

これに対する一般的な解決策は、テンプレート宣言をヘッダーファイルに記述してから、クラスを実装ファイル(たとえば、.tpp)に実装し、この実装ファイルをヘッダーの最後に含めることです。

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

このようにして、実装はまだ宣言から分離されていますが、コンパイラからアクセス可能です。

もう1つの解決策は、実装を分離しておき、必要なすべてのテンプレートインスタンスを明示的にインスタンス化することです。

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

私の説明が十分に明確でないならば、あなたはこの主題に関する C++スーパーFAQを見ることができる

1364
Luc Touraille

ここにはたくさんの正解がありますが、私はこれを追加したいと思いました(完全を期すため)。

実装のcppファイルの最後で、テンプレートが使用されるすべての型の明示的なインスタンス化を行うと、リンカは通常どおりそれらを見つけることができます。

編集:明示的なテンプレートのインスタンス化の例を追加。テンプレートが定義され、すべてのメンバ関数が定義された後に使用されます。

template class vector<int>;

これにより、クラスとそのすべてのメンバ関数がインスタンス化されます(したがって、リンカが利用できるようになります)。テンプレート関数に対しても同様の構文が機能するため、メンバー以外の演算子がオーバーロードされている場合は、それらについても同じ操作を行う必要があります。

Vectorがヘッダーで完全に定義されているので、上記の例はあまり役に立ちません。ただし、共通のインクルードファイル(プリコンパイル済みヘッダー?)がextern template class vector<int>を使用してすべてのother(1000?)ファイルでインスタンス化されるのを防ぎます。それはベクトルを使用します。

226
MaHuJa

これは、個別のコンパイルが必要であり、テンプレートはインスタンス化スタイルの多態性であるためです。

説明のためにもう少し具体的に説明しましょう。次のファイルがあるとします。

  • foo.h
    • class MyClass<T>のインターフェースを宣言します
  • foo.cpp
    • class MyClass<T>の実装を定義します
  • bar.cpp
    • MyClass<int>を使用します

別々のコンパイルとはfoo.cppbar.cppとは独立してコンパイルできるはずです。コンパイラーは、各コンパイル単位で分析、最適化、およびコード生成のすべてのハードワークを完全に独立して行います。プログラム全体の分析をする必要はありません。プログラム全体を一度に処理する必要があるのはリンカだけです。リンカの仕事はかなり簡単です。

foo.cppをコンパイルするとき、bar.cppさえ存在する必要はありませんが、foo.obarと一緒にリンクすることができるはずです。 .o私は、foo.cppを再コンパイルする必要なしに、作成したばかりです。 foo.cppを動的ライブラリにコンパイルし、foo.cppなしでどこかに配布し、私がfoo.cppを書いてから何年も経ってから書くコードとリンクすることもできます。

「インスタンス化スタイルの多態性」とは、テンプレートMyClass<T>が、実際には任意の値のTに対して機能するコードにコンパイルできる汎用クラスではないことを意味します。 C++テンプレートの目的は、ほぼ同一のclass MyClass_intclass MyClass_floatなどを記述しなくても、コンパイル済みコードで済むようにすることです。それはまるでhadがそれぞれのバージョンを別々に書いたかのようです。テンプレートは文字通りテンプレートです。クラステンプレートはnotクラスです。これは出会ったそれぞれのTに対して新しいクラスを作成するレシピです。テンプレートをコードにコンパイルすることはできません。テンプレートをインスタンス化した結果のみをコンパイルできます。

したがって、foo.cppがコンパイルされると、コンパイラはbar.cppを見てMyClass<int>が必要であることを知ることができません。テンプレートMyClass<T>は見えますが、そのためのコードは発行できません(これはテンプレートであり、クラスではありません)。また、bar.cppがコンパイルされると、コンパイラはMyClass<int>を作成する必要があることを認識できますが、テンプレートMyClass<T>foo.h内のインタフェースのみ)は表示されません。それを作成しません。

foo.cpp自体がMyClass<int>を使用している場合、そのためのコードはfoo.cppのコンパイル中に生成されるため、bar.ofoo.oにリンクされている場合接続されて動作します。その事実を利用して、単一のテンプレートを記述することによって、テンプレートインスタンス化の有限セットを.cppファイルに実装することができます。しかし、bar.cppがテンプレートテンプレートとしてを使用して、それが好きな型でインスタンス化する方法はありません。 foo.cppの作者が提供しようと思っていたテンプレートクラスの既存のバージョンしか使えません。

あなたは、テンプレートをコンパイルするとき、コンパイラは「すべてのバージョンを生成する」べきだと思うかもしれません。大きなオーバーヘッドと、ポインタや配列のような "型修飾子"機能によって、無限の型を生み出すことができるので、このようなアプローチが直面するであろう極端な困難とは別に、私が今私のプログラムを拡張するとき何が起こるか追加して:

  • baz.cpp
    • class BazPrivateを宣言および実装し、MyClass<BazPrivate>を使用

これがうまくいかない限り、これが機能する可能性はありません。

  1. プログラム内の他のファイルを変更するたびにfoo.cppを再コンパイルする必要があります。これはMyClass<T>の新しい新しいインスタンスが追加された場合に備えます。
  2. baz.cppのコンパイル時にコンパイラがMyClass<T>を生成できるように、baz.cppMyClass<BazPrivate>の完全なテンプレートを(おそらくヘッダーインクルード経由で)含めることを要求します。

(1)が好きな人は誰もいません。全プログラム分析コンパイルシステムはコンパイルに永遠を必要とし、コンパイルされたライブラリをソースコードなしで配布することは不可能だからです。そのため、代わりに(2)があります。

209
Ben

テンプレートは、実際にそれらをオブジェクトコードにコンパイルする前に、 インスタンス化 される必要があります。このインスタンス化は、テンプレートの引数がわかっている場合にのみ実現できます。テンプレート関数がa.hで宣言され、a.cppで定義され、b.cppで使用されるシナリオを想像してください。 a.cppがコンパイルされるとき、次回のコンパイルb.cppがテンプレートのインスタンスを必要とすることが必ずしも知られているわけではありません。ヘッダファイルやソースファイルが増えると、状況はすぐに複雑になる可能性があります。

テンプレートのすべての用途についてコンパイラを「先読み」するために賢くすることができますが、再帰的または複雑なシナリオを作成するのは難しいことではないと確信しています。私の知る限りでは、コンパイラはそのような見通しをしていません。 Antonが指摘したように、一部のコンパイラはテンプレートインスタンス化の明示的なエクスポート宣言をサポートしていますが、すべてのコンパイラがサポートしているわけではありません(まだ?)。

71
David Hanak

実際、C++ 11より前の標準では、になるexportキーワードが定義されており、ヘッダーファイルでテンプレートを宣言して他の場所でそれらを実装することができます。

このキーワードを実装している人気のあるコンパイラはありません。私が知っている唯一のものは、Comeau C++コンパイラによって使用されるEdison Design Groupによって書かれたフロントエンドです。他のすべてのユーザーは、ヘッダーファイルにテンプレートを記述する必要がありました。これは、コンパイラが適切なインスタンス化のためにテンプレート定義を必要とするためです(他のユーザーが既に指摘したとおり)。

その結果、ISO C++標準委員会は、C++ 11ではテンプレートのexport機能を削除することを決定しました。

58
DevSolar

標準C++にはそのような要件はありませんが、一部のコンパイラでは、使用されるすべての翻訳単位ですべての関数テンプレートとクラステンプレートを利用できるようにする必要があります。実際には、これらのコンパイラでは、テンプレート関数の本体をヘッダーファイルで使用可能にする必要があります。繰り返しますが、それはそれらのコンパイラがそれらが.cppファイルのような非ヘッダファイルで定義されることを許可しないことを意味します

この問題を軽減すると思われる export というキーワードがありますが、移植性があるとは言えません。

34
Anton Gogolev

テンプレートパラメータに対して与えられたパラメータまたは推​​定されたパラメータに応じて、コンパイラはコードのさまざまなバージョンをインスタンス化する必要があるため、テンプレートはヘッダで使用する必要があります。テンプレートはコードを直接表すのではなく、そのコードのいくつかのバージョン用のテンプレートです。テンプレート以外の関数を.cppファイルにコンパイルすると、具象関数/クラスがコンパイルされます。これはテンプレートの場合には当てはまりません。テンプレートはさまざまな型でインスタンス化できます。つまり、テンプレートパラメータを具体的な型に置き換えるときに具体的なコードを発行する必要があります。

exportキーワードには、個別のコンパイルに使用されることを意図した機能がありました。 export機能はC++11で廃止され、AFAIKでは、1つのコンパイラだけがそれを実装しました。 exportを使うべきではありません。 C++C++11では別々のコンパイルはできませんが、C++17では多分、概念がそれをうまく組み込めば、何らかの方法で別々のコンパイルができるでしょう。

別々のコンパイルを達成するためには、別々のテンプレート本体のチェックが可能でなければなりません。解決策は概念で可能であると思われる。標準委員会で最近発表された paper を見てください。ユーザーコードでテンプレートコードのコードをインスタンス化する必要があるので、これが唯一の要件ではないと思います。

テンプレート用の個別のコンパイルの問題これは、現在機能しているモジュールへの移行に伴って発生している問題でもあると思います。

27
Germán Diago

つまり、テンプレートクラスのメソッド実装を定義する最も移植性の高い方法は、テンプレートクラス定義内でそれらを定義することです。

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};
15
Benoît

上には十分な説明がたくさんありますが、テンプレートをヘッダーと本文に分ける実用的な方法が欠けています。
私の主な関心事は、定義を変更したときに、すべてのテンプレートユーザーの再コンパイルを回避することです。
[。]。テンプレートの本体にすべてのテンプレートのインスタンス化を含めることは、私にとっては現実的な解決策ではありません。テンプレートの作成者はその使用方法とテンプレートユーザーが変更する権利を持っていないかどうかを知らないからです。
私は以下のアプローチを取りましたが、これは古いコンパイラでもうまくいきます(gcc 4.3.4、aCC A.03.13)。

テンプレートを使用するたびに、独自のヘッダーファイル(UMLモデルから生成される)にtypedefがあります。その本体にはインスタンス化が含まれています(これは最後にリンクされているライブラリになります)。
テンプレートの各ユーザーはそのヘッダーファイルをインクルードし、typedefを使用します。

概略的な例:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

MyTemplate.cpp:

#include "MyTemplate.h"
#include <iostream>

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

MyInstantiatedTemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

MyInstantiatedTemplate.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

main.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

この方法では、すべてのテンプレートユーザー(および依存関係)ではなく、テンプレートのインスタンス化のみを再コンパイルする必要があります。

12
lafrecciablu

それを使用しているすべての.cppモジュールの一部として.hをコンパイルすることによって生じる余分なコンパイル時間とバイナリサイズの増大が懸念される場合、多くの場合、テンプレートクラスを非テンプレート化基本クラスから継承することができます。インターフェイスの型に依存しない部分、およびその基本クラスは.cppファイルに実装を含めることができます。

6
Eric Shaw

コンパイラーは、それが割り振り用のタイプであることを知っていなければならないので、これは正確に正しいです。そのため、テンプレートクラス、関数、列挙型などは、ヘッダファイルがc/cppファイルとは異なりコンパイルされないため、ヘッダファイルをパブリックまたはライブラリの一部(静的または動的)にする場合はヘッダファイルにも実装する必要がありますあります。コンパイラがその型がそれをコンパイルできないことを知らない場合。 .NETでは、すべてのオブジェクトがObjectクラスから派生しているためです。これは.NETではありません。

6
Robert

ここで注目に値するものを追加するだけです。テンプレートクラスのメソッドが関数テンプレートではない場合は、実装ファイル内で問題なく定義できます。


myQueue.hpp:

template <class T> 
class QueueA {
    int size;
    ...
public:
    template <class T> T dequeue() {
       // implementation here
    }

    bool isEmpty();

    ...
}    

myQueue.cpp:

// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
    return this->size == 0;
}


main()
{
    QueueA<char> Q;

    ...
}
2
Nikos

別々に実装する方法は次のとおりです。

//inner_foo.h

template <typename T>
struct Foo
{
    void doSomething(T param);
};


//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}


//foo.h
#include <foo.tpp>

//main.cpp
#include <foo.h>

inner_fooには前方宣言があります。 foo.tppはインプリメンテーションを持ち、inner_foo.hをインクルードします。 foo.tppをインクルードするために、foo.hには1行しかありません。

コンパイル時には、foo.hの内容がfoo.tppにコピーされ、次にファイル全体がfoo.hにコピーされてからコンパイルされます。このように、1つの追加ファイルと引き換えに、制限はなく、命名も一貫しています。

* .tppのクラスの前方宣言が見られないときにコードの静的アナライザが壊れるので、これを行います。これは、IDEでコードを書くとき、またはYouCompleteMeなどを使用するときに面倒です。

2
Pranay

コンパイルステップでテンプレートを使用すると、コンパイラはテンプレートのインスタンス化ごとにコードを生成します。 main.cppに含まれている.hファイルには実装YETがないため、コンパイルおよびリンクプロセスでは、.cppファイルは純粋なオブジェクトまたはマシンコードに変換され、その中に参照または未定義のシンボルが含まれます。これらはあなたのテンプレートのための実装を定義する別のオブジェクトファイルとリンクされる準備ができているので、あなたは完全なa.out実行可能ファイルを持っています。ただし、メインプログラムで行うテンプレートのインスタンス化ごとにコードを生成するには、テンプレートをコンパイルステップで処理する必要があるため、main.cppをmain.oにコンパイルしてからテンプレート.cppをコンパイルするため、リンクは役に立ちません。異なるテンプレートのインスタンス化を同じテンプレート実装にリンクしているので、template.oにリンクしてからリンクしてもテンプレートの目的は達成されません。そしてテンプレートは反対のことをする、すなわち一つの実装を持つが一つのクラスの使用を通して多くの利用可能なインスタンス化を可能にすることになっています。

意味typename T getはリンクステップではなくコンパイルステップ中に置き換えられるので、Tを具体的な値の型として置き換えずにテンプレートをコンパイルしようとすると、テンプレートの定義がコンパイル時プロセスになるので動作しません。プログラミングとは、この定義を使用することだけです。

1
Moshe Rabaev

宣言と定義の両方をヘッダーファイルに記述することをお勧めするもう1つの理由は、読みやすさのためです。 Utility.hにそのようなテンプレート関数があるとします。

template <class T>
T min(T const& one, T const& theOther);

そして、Utility.cppでは:

#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
    return one < other ? one : other;
}

これは、ここですべてのTクラスに小なり演算子(<)を実装することを要求します。 "<"を実装していない2つのクラスインスタンスを比較すると、コンパイラエラーが発生します。

したがって、テンプレートの宣言と定義を分離した場合、このAPIを独自のクラスで使用するためにヘッダーファイルを読み取ってこのテンプレートの内外を確認することはできません。どの演算子をオーバーライドする必要があるかについてのケース。

0
ClarHandsome