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


1778

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

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

どうしてこれなの?

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


13
すべてのテンプレート関数定義をヘッダーファイルに配置することはおそらくそれらを使用する最も便利な方法であることは事実ですが、その引用で「インライン」が何をしているのかはまだ明確ではありません。そのためにインライン関数を使用する必要はありません。「インライン」はこれとはまったく関係ありません。
AnT、2014

7
本は古くなっています。
gerardw 2014年

1
テンプレートは、バイトコードにコンパイルできる関数のようなものではありません。このような関数を生成するための単なるパターンです。テンプレートを単独で* .cppファイルに入れると、コンパイルするものはありません。さらに、明示的なインスタンス化は実際にはテンプレートではなく、*。objファイルに含まれるテンプレートから関数を作成するための開始点です。
dgrat

5
テンプレートコンセプトがこのためにC ++で機能しなくなっていると感じているのは私だけですか?
DragonGamer

回答:


1558

警告:ヘッダーファイルに実装を含める必要はありません。この回答の最後にある代替ソリューションを参照してください。

とにかく、コードが失敗する理由は、テンプレートをインスタンス化するときに、コンパイラが指定されたテンプレート引数を使用して新しいクラスを作成するためです。例えば:

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
}

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

代替ソリューション

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

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 ++ Super-FAQをご覧ください


96
実際、明示的なインスタンス化は、ヘッダーではなく、Fooのすべてのメンバー関数の定義にアクセスできる.cppファイルに存在する必要があります。
マンカルス

11
「コンパイラーは、テンプレート引数(この場合はint)でインスタンス化するために、メソッドの実装にアクセスできる必要があります。これらの実装がヘッダーになかった場合、アクセスできません」しかし、なぜ実装はコンパイラが.cppファイルにアクセスできませんか?コンパイラは.cpp情報にもアクセスできますが、他にどのようにして.objファイルに変換しますか?編集:この質問への回答はこの回答で提供されているリンクにあります...
xcrypt

31
私は、これは明らかに、重要なことは、明らかに、この記事で言及されていないコンパイルユニットと関連していることを質問を説明するとは思わない
zinking

6
@Gabson:構造体とクラスは同等ですが、クラスのデフォルトのアクセス修飾子は「プライベート」ですが、構造体ではパブリックです。この質問を見れば、他にもいくつかの小さな違いを学ぶことができます。
Luc Touraille、2014

3
この回答の冒頭に文を追加して、質問が誤った前提に基づいていることを明確にしました。誰かが「なぜXは本当なのか?」実際にXが真でない場合、その仮定をすばやく拒否する必要があります。
Aaron McDaid

250

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

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

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

template class vector<int>;

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

上記の例は、ベクターがヘッダーで完全に定義されているため、ほとんど役に立ちません。ただし、共通のインクルードファイル(プリコンパイル済みヘッダー?)がベクターを使用する他のextern template class vector<int>すべて(1000?)ファイルでインスタンス化されないようにする場合を除きます。


51
ああ。良い答えですが、実際のクリーンなソリューションはありません。テンプレートのすべての可能なタイプをリストすることは、テンプレートが想定されているものと一致しないようです。
ジミニオン2014

6
これは多くの場合に役立ちますが、通常はtype手動でリストしなくてもクラスを使用できるようにすることを目的としたテンプレートの目的に反します。
トマーシュZato -復活モニカ

7
vectorコンテナは本質的に「すべての」タイプをターゲットにしているため、良い例ではありません。ただし、特定のタイプのセット(例えば、数値タイプ:int8_t、int16_t、int32_t、uint8_t、uint16_tなど)専用のテンプレートを作成することは非常に頻繁に発生します。この場合でも、テンプレートを使用することには意味があります、しかし、型のセット全体に対してそれらを明示的にインスタンス化することも可能であり、私の意見では推奨されています。
UncleZeiv 2015年

テンプレートが定義された後に使用され、「すべてのメンバー関数が定義されました」。よろしくお願いします!
Vitt Volt

1
何かが足りないように感じます…2つの型の明示的なインスタンス化をクラスの.cppファイルに入れ、2つのインスタンス化が他の.cppファイルから参照されていますが、それでもメンバーが見つからないというリンクエラーが発生します。
oarfish

250

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

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

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

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

bar.cppはさえ、私はコンパイル時に存在する必要はありませんfoo.cppを、私はまだリンクすることができるはずfoo.oの私はすでにと一緒にいたbar.o再コンパイルする必要がなく、私はちょうど生成しましたが、FOO .cppfoo.cppは、動的ライブラリにコンパイルし、foo.cppなしで別の場所に配布し、私がfoo.cppを作成してから数年後に作成したコードにリンクすることもできます。

「インスタンス化スタイルのポリモーフィズム」とは、テンプレートMyClass<T>が実際には、任意の値で機能するコードにコンパイルできる汎用クラスではないことを意味しますT。これにより、ボクシング、アロケーターやコンストラクターに関数ポインターを渡す必要などのオーバーヘッドが追加されます。C++テンプレートの目的はclass MyClass_int、ほぼ同一のclass MyClass_float、などを記述する必要を回避することですが、次のようなコンパイル済みコードになる可能性があります。私たちは、主にいるかのようにしていた個別の各バージョンが書かれて。したがって、テンプレートは文字通りテンプレートです。クラステンプレートはクラスではありません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のコンパイル中に生成できるように、baz.cppにの完全なテンプレートが(ヘッダーインクルードを介して)含まれている必要があります。MyClass<T>MyClass<BazPrivate>

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


50
強調された引用テンプレートは文字通りテンプレートです。クラステンプレートクラスではありません、それはそれぞれのT私たちの出会いのための新しいクラスを作成するためのレシピです
v.oddou

知りたいのですが、クラスのヘッダーまたはソースファイル以外の場所から明示的なインスタンス化を行うことはできますか?たとえば、main.cppでそれらを実行しますか?
gromit190 2017年

1
@Birger完全なテンプレート実装にアクセスできる任意のファイルからそれを実行できるはずです(同じファイル内にあるか、ヘッダーインクルードを介して)。
ベン

11
@ajehレトリックではありません。問題は、「なぜヘッダーにテンプレートを実装する必要があるのか​​」ということです。そこで、C ++言語がこの要件に導く技術的な選択を説明しました。私が回答を書く前に、完全なソリューションはあり得ないため、他の人はすでに完全なソリューションではない回避策を提供していました。これらの答えは、質問の「なぜ」という角度のより完全な議論によって補完されると感じました。
ベン

1
このように想像してみてください...(必要なものを効率的にコーディングするために)テンプレートを使用していなかったとしても、とにかくそのクラスのいくつかのバージョンのみを提供することになります。したがって、3つのオプションがあります。1)。テンプレートを使用しないでください。(他のすべてのクラス/関数と同様に、他の人が型を変更できないことを誰も気にしません)2)。テンプレートを使用し、使用できるタイプを文書化します。3)。実装全体(ソース)のボーナスを与えます4)。クラスの別の1つからテンプレートを作成する場合に備えて、ソース全体を提供します;)
Puddle

81

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

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


1
「エクスポート」は標準ですが、実装するのが難しいので、ほとんどのコンパイラチームはまだ完了していません。
ヴァバ2009年

5
エクスポートは、ソースの開示の必要性をなくすことも、コンパイルの依存関係を減らすこともありませんが、コンパイラビルダーによる多大な労力を必要とします。そのため、ハーブサッター自身がコンパイラビルダーにエクスポートを「忘れる」ように依頼しました。必要な時間投資は他の場所で使う方が良いので...
ピーター

2
ですから、エクスポートはまだ実装されていないと思います。他の人がどれくらいの時間を要し、どれだけの利益が得られたかを見た後は、EDG以外の誰かがそれを完了することはおそらくないでしょう
Pieter

3
それがあなたに興味があるなら、その紙は「なぜ輸出ができないのか」と呼ばれ、彼のブログ(gotw.ca/publications)に記載されていますが、そこにはpdfがありません(グーグルですぐに確認してください)
Pieter

1
良い例と説明をありがとう。ここで私の質問ですが、なぜテンプレートが呼び出されているのかコンパイラが理解できず、定義ファイルをコンパイルする前にそれらのファイルを最初にコンパイルできないのですか?私はそれが簡単なケースで実行できると想像できます...相互依存が注文をかなり早く混乱させるという答えはありますか?
2013年

62

実際、C ++ 11より前のexportバージョンで、ヘッダーファイルでテンプレートを宣言して他の場所に実装できるようにするキーワードが標準で定義されていました。

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

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


6
...そして数年後、私は最終的exportに実際に何が私たちに与えられ、何が与えられなかったかを理解しました...そして今、私はEDGの人々に心から同意します:それはほとんどの人々('11含まれています)そう思う、そしてC ++標準はそれなしでより良いです。
DevSolar 2015年

4
@DevSolar:このペーパーは政治的で繰り返しが多く、ひどく書かれています。それは通常の標準レベルの散文ではありません。何十ページにもわたって、基本的に同じことを3倍も言ってしまうほど、長くて退屈です。しかし、私は今、輸出は輸出ではないことを知らされています。それは良い情報です!
v.oddou

1
@ v.oddou:優れた開発者と優れたテクニカルライターは、2つの別々のスキルセットです。両方ができる人もいれば、できない人もいます。;-)
DevSolar

@ v.oddouこの論文は単にひどく書かれただけではなく、偽情報です。また、これは現実のスピンでもあります。実際、輸出に対する非常に強力な主張は、輸出に反するように混合されています。「輸出の存在下で規格にあるODR関連の穴を多数発見する。エクスポートする前に、ODR違反をコンパイラーで診断する必要はありませんでした。異なる翻訳単位からの内部データ構造を組み合わせる必要があり、それらが実際に異なるものを表している場合はそれらを組み合わせることができないため、チェックを行う必要があるため、これが必要です。」
curiousguy

" 発生したときの翻訳単位を追加する必要があります。" 足りない引数を使用せざるを得ない場合、引数はありません。もちろん、エラーでファイル名について言及するつもりですが、何が問題ですか?誰もがそのBSに陥るというのは、気が遠くなるほどで​​す。「ジェームズ・カンゼのような専門家でさえ、輸出が実際にこのようなものであることを受け入れるのは難しいと感じています。」何?!!!!
好奇心旺盛な男

34

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

この問題を軽減することになっているエクスポートキーワードがありますが、移植性にはほど遠いものです。


「インライン」というキーワードで.cppファイルにそれらを実装できないのはなぜですか?
MainID 2009年

2
できますし、「インライン」を配置する必要もありません。しかし、それらはそのcppファイルでのみ使用でき、他では使用できません。
ヴァバ2009年

10
これは、「それらのコンパイラーが.cppファイルなどの非ヘッダーファイルでの定義を許可しないことを意味する」を除いて、ほぼ正確な答えです。
オービットでの軽さのレース

28

コンパイラーは、テンプレートパラメーターに指定または推定されたパラメーターに応じて、コードの異なるバージョンをインスタンス化する必要があるため、テンプレートはヘッダーで使用する必要があります。テンプレートはコードを直接表すのではなく、そのコードのいくつかのバージョンのテンプレートであることを忘れないでください。.cppファイル内の非テンプレート関数をコンパイルすると、具体的な関数/クラスがコンパイルされます。これは、異なるタイプでインスタンス化できるテンプレートには当てはまりません。つまり、テンプレートパラメーターを具象型に置き換えるときに具象コードを生成する必要があります。

export個別のコンパイルに使用するためのキーワードを備えた機能がありました。このexport機能は非推奨でC++11あり、AFAIKでは、1つのコンパイラのみがこの機能を実装しています。は使用しないでくださいexport。分割コンパイルは可能ではないC++か、C++11多分にC++17概念は、それを作る場合、私たちは別のコンパイルのいくつかの方法を持つことができ、。

個別のコンパイルを実現するには、個別のテンプレート本文チェックが可能でなければなりません。コンセプトがあれば解決できるようです。標準委員会で最近発表されたこのペーパーをご覧ください。ユーザーコードでテンプレートコードのコードをインスタンス化する必要があるため、これが唯一の要件ではないと思います。

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


15

つまり、テンプレートクラスのメソッド実装を定義する最もポータブルな方法は、テンプレートクラス定義内でそれらを定義することです。

template < typename ... >
class MyClass
{

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

15

上記の説明はたくさんありますが、テンプレートをヘッダーと本文に分離する実用的な方法がありません。
私の主な関心事は、定義を変更するときに、すべてのテンプレートユーザーの再コンパイルを回避することです。
すべてのテンプレートのインスタンス化をテンプレート本文に含めることは、私にとって実行可能な解決策ではありません。テンプレートの作成者は、その使用法とテンプレートユーザーがそれを変更する権限を持っていない場合、すべてを知ることができないためです。
私は、古いコンパイラ(gcc 4.3.4、aCC A.03.13)でも機能する次のアプローチを採用しました。

テンプレートの使用ごとに、独自のヘッダーファイルにtypedefがあります(UMLモデルから生成されます)。その本体にはインスタンス化が含まれます(最後にリンクされるライブラリに終わります)。
テンプレートの各ユーザーは、そのヘッダーファイルをインクルードし、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;
}

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


1
MyInstantiatedTemplate.hファイルと追加されたMyInstantiatedTemplateタイプを除いて、私はこのアプローチが好きです。あなたがそれを使わないなら、それは私見で少しきれいです。これを示す別の質問で私の答えを確認してください:stackoverflow.com/a/41292751/4612476
Cameron Tacklind

これは、2つの世界のうち最良のものを使用します。私はこの答えがより高く評価されたことを望みます!同じアイデアの少しわかりやすい実装については、上記のリンクも参照してください。
ウォーマー、

8

ここで注目すべきものを追加します。テンプレート化されたクラスのメソッドは、関数テンプレートではない場合でも、実装ファイルで問題なく定義できます。


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
実際の人にとっては???それが本当であるなら、あなたの答えは正しいものとしてチェックされるべきです。
マイケルIV

少なくともMSVC 2019では、テンプレートクラスのメンバー関数の未解決の外部シンボルを取得しています。
マイケルIV

テストするMSVC 2019がありません。これはC ++標準で許可されています。現在、MSVCは常にルールを順守しているわけではないことで有名です。まだ行っていない場合は、[プロジェクトの設定]-> [C / C ++]-> [言語]-> [準拠モード]-> [はい(許容-)]を試してください。
ニコス

1
この正確な例は機能しますがisEmpty、それ以外の翻訳ユニットからは呼び出せませんmyQueue.cpp...
MM

7

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


2
この応答は、さらに修正する必要があります。私は「独立して」あなたの同じアプローチを発見し、それを公式のパターンであるかどうか、そして名前が付けられているかどうかに興味があるので、すでに誰か他の人がすでにそれを使用しているのを探していました。私のアプローチは、を実装するclass XBase必要がある場所に実装しtemplate class X、型に依存する部分を入れX、残りをすべて入れXBaseます。
Fabio A.

6

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


5
「ヘッダファイルはコンパイルされていません」-それはそれを説明する非常に奇妙な方法です。ヘッダーファイルは、「c / cpp」ファイルのように、翻訳単位の一部にすることができます。
フレキソ

2
実際、これは真実とは正反対です。つまり、ヘッダーファイルは非常に頻繁にコンパイルされますが、ソースファイルは通常1回コンパイルされます。
xaxxon

6

コンパイラーは、コンパイル・ステップ中にテンプレートを使用すると、テンプレートのインスタンス化ごとにコードを生成します。main.cppに含まれている.hファイルにはまだ実装されていないため、コンパイルおよびリンクプロセスでは、.cppファイルは参照または未定義のシンボルを含む純粋なオブジェクトまたはマシンコードに変換されます。これらは、テンプレートの実装を定義する別のオブジェクトファイルとリンクする準備ができているため、完全なa.out実行可能ファイルがあります。

ただし、テンプレートは、定義するテンプレートのインスタンス化ごとにコードを生成するためにコンパイルステップで処理する必要があるため、ヘッダーファイルとは別にテンプレートをコンパイルするだけでは機能しません。理由は、テンプレートが常に関係しているためです。各テンプレートのインスタンス化は文字通りまったく新しいクラスです。通常のクラスでは、.hと.cppを分離できます。.hはそのクラスの設計図であり、.cppは未加工の実装であるため、実装ファイルを定期的にコンパイルおよびリンクできますが、テンプレート.hを使用すると、クラスはオブジェクトがどのように見えるかではなく、テンプレート.cppファイルはクラスの生の通常の実装ではなく、単なるクラスの青写真であるため、.hテンプレートファイルの実装はすべて

したがって、テンプレートは個別にコンパイルされることはなく、他のソースファイルに具体的なインスタンス化がある場合にのみコンパイルされます。ただし、具体的なインスタンス化では、テンプレートファイルの実装を知っている必要があります。typename T.hファイルで具象型を使用しても、機能しません。リンクする.cppがあるため、テンプレートは抽象的でコンパイルできないため、後で見つけることができません。実装をすぐに提供して、コンパイルとリンクの対象を理解し、実装を囲んでいるソースファイルにリンクするようにしました。基本的に、テンプレートをインスタンス化する瞬間に、まったく新しいクラスを作成する必要があります。コンパイラに通知しない限り、提供する型を使用するときにそのクラスがどのように見えるかわからない場合は、それを行うことができません。テンプレートの実装なので、コンパイラーはT自分のタイプに置き換えて、コンパイルしてリンクする準備ができている具象クラスを作成できます。

要約すると、テンプレートはクラスの外観の青写真であり、クラスはオブジェクトの外観の青写真です。コンパイラは具象型のみをコンパイルするため、テンプレートを具体的なインスタンス化とは別にコンパイルすることはできません。つまり、少なくともC ++のテンプレートは純粋な言語の抽象化です。いわばテンプレートの抽象化を解除する必要があります。テンプレートの抽象化が通常のクラスファイルに変換され、通常どおりにコンパイルできるように、それらに処理​​する具体的な型を与えることによってそうします。テンプレート.hファイルとテンプレート.cppファイルを分離しても意味がありません。.cppと.hだけの分離は、.cppを個別にコンパイルして個別にリンクできる場所だけなので、無意味です。テンプレートは抽象的であるため、個別にコンパイルできないため、テンプレートを使用します。

意味typename Tはリンクのステップではなくコンパイルのステップで置き換えられるのでT、コンパイラーにとって完全に意味のない具体的な値の型として置き換えられずにテンプレートをコンパイルしようとすると、結果としてオブジェクトコードを作成できません。何を知っていTます。

技術的には、template.cppファイルを保存し、他のソースでそれらを見つけたときにタイプを切り替える何らかの機能を作成するexportことが可能です。標準には、テンプレートを個別に配置できるようにするキーワードがあると思いますcppファイルですが、実際には多くのコンパイラがこれを実装していません。

余談ですが、テンプレートクラスの特殊化を行う場合、ヘッダーを実装から分離できます。これは、定義による特殊化とは、個別にコンパイルおよびリンクできる具象型に特化しているためです。


4

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

//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.hには、foo.tppを含めるための1行のみが含まれます。

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

これは、コードの静的アナライザーが* .tppのクラスの前方宣言を認識しないと壊れるためです。これは、任意のIDEでコードを記述したり、YouCompleteMeなどを使用したりするときに煩わしいものです。


2
s / inner_foo / foo / gおよびfoo.hの末尾にfoo.tppを含めます。ファイルが1つ少なくなります。

1

テンプレートのインスタンス化のための「cfront」モデルと「borland」モデルのトレードオフについて説明しているこのgccページをご覧になることをお勧めします。

https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html

「ボーランド」モデルは、作成者の提案に対応し、完全なテンプレート定義を提供し、物事を複数回コンパイルします。

手動および自動のテンプレートのインスタンス化の使用に関する明確な推奨事項が含まれています。たとえば、「-repo」オプションを使用して、インスタンス化する必要のあるテンプレートを収集できます。または、「-fno-implicit-templates」を使用してテンプレートの自動インスタンス化を無効にし、手動でテンプレートをインスタンス化することもできます。

私の経験では、(テンプレートライブラリを使用して)各コンパイルユニットに対してインスタンス化されるC ++標準ライブラリとBoostテンプレートに依存しています。大規模なテンプレートクラスの場合、必要なタイプに対して手動でテンプレートをインスタンス化します。

他のプログラムで使用するためのテンプレートライブラリではなく、実際に動作するプログラムを提供しているため、これが私のアプローチです。この本の作者であるJosuttisは、テンプレートライブラリの作成に多く取り組んでいます。

速度が本当に心配だったら、プリコンパイル済みヘッダーの使用を検討することになります。 ますhttps://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

これは多くのコンパイラでサポートを得ています。ただし、テンプレートヘッダーファイルでは、プリコンパイル済みヘッダーは難しいと思います。


-2

ヘッダーファイルに宣言と定義の両方を書き込むことをお勧めするもう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を独自のクラスで使用するために、ヘッダーファイルを読み取ってこのテンプレートの入出力を確認することだけはできませんオーバーライドする必要のある演算子に関するケース。


-7

テンプレートクラスは、.cppファイルではなく.templateファイル内で実際に定義できます。ヘッダーファイル内でのみ定義できると言っている人は誰でも間違っています。これは、c ++ 98までさかのぼって機能するものです。

コンパイラーが.templateファイルをc ++ファイルとして扱い、インテリセンスを維持することを忘れないでください。

以下は、動的配列クラスの例です。

#ifndef dynarray_h
#define dynarray_h

#include <iostream>

template <class T>
class DynArray{
    int capacity_;
    int size_;
    T* data;
public:
    explicit DynArray(int size = 0, int capacity=2);
    DynArray(const DynArray& d1);
    ~DynArray();
    T& operator[]( const int index);
    void operator=(const DynArray<T>& d1);
    int size();

    int capacity();
    void clear();

    void push_back(int n);

    void pop_back();
    T& at(const int n);
    T& back();
    T& front();
};

#include "dynarray.template" // this is how you get the header file

#endif

次に、.templateファイル内で、通常どおりに関数を定義します。

template <class T>
DynArray<T>::DynArray(int size, int capacity){
    if (capacity >= size){
        this->size_ = size;
        this->capacity_ = capacity;
        data = new T[capacity];
    }
    //    for (int i = 0; i < size; ++i) {
    //        data[i] = 0;
    //    }
}

template <class T>
DynArray<T>::DynArray(const DynArray& d1){
    //clear();
    //delete [] data;
    std::cout << "copy" << std::endl;
    this->size_ = d1.size_;
    this->capacity_ = d1.capacity_;
    data = new T[capacity()];
    for(int i = 0; i < size(); ++i){
        data[i] = d1.data[i];
    }
}

template <class T>
DynArray<T>::~DynArray(){
    delete [] data;
}

template <class T>
T& DynArray<T>::operator[]( const int index){
    return at(index);
}

template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
    if (this->size() > 0) {
        clear();
    }
    std::cout << "assign" << std::endl;
    this->size_ = d1.size_;
    this->capacity_ = d1.capacity_;
    data = new T[capacity()];
    for(int i = 0; i < size(); ++i){
        data[i] = d1.data[i];
    }

    //delete [] d1.data;
}

template <class T>
int DynArray<T>::size(){
    return size_;
}

template <class T>
int DynArray<T>::capacity(){
    return capacity_;
}

template <class T>
void DynArray<T>::clear(){
    for( int i = 0; i < size(); ++i){
        data[i] = 0;
    }
    size_ = 0;
    capacity_ = 2;
}

template <class T>
void DynArray<T>::push_back(int n){
    if (size() >= capacity()) {
        std::cout << "grow" << std::endl;
        //redo the array
        T* copy = new T[capacity_ + 40];
        for (int i = 0; i < size(); ++i) {
            copy[i] = data[i];
        }

        delete [] data;
        data = new T[ capacity_ * 2];
        for (int i = 0; i < capacity() * 2; ++i) {
            data[i] = copy[i];
        }
        delete [] copy;
        capacity_ *= 2;
    }
    data[size()] = n;
    ++size_;
}

template <class T>
void DynArray<T>::pop_back(){
    data[size()-1] = 0;
    --size_;
}

template <class T>
T& DynArray<T>::at(const int n){
    if (n >= size()) {
        throw std::runtime_error("invalid index");
    }
    return data[n];
}

template <class T>
T& DynArray<T>::back(){
    if (size() == 0) {
        throw std::runtime_error("vector is empty");
    }
    return data[size()-1];
}

template <class T>
T& DynArray<T>::front(){
    if (size() == 0) {
        throw std::runtime_error("vector is empty");
    }
    return data[0];
    }

2
ほとんどの人は、ヘッダーファイルを定義をソースファイルに伝播するものとして定義します。したがって、ファイル拡張子「.template」を使用することを決定した可能性がありますが、ヘッダーファイルを作成しました。
トミー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.