externテンプレートの使用(C ++ 11)


116

図1: 関数テンプレート

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

これはを使用する正しい方法ですかextern template、またはこのキーワードを図2のようにクラステンプレートにのみ使用しますか?

図2:クラステンプレート

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

これらすべてを1つのヘッダーファイルに入れるのは良いことですが、複数のファイルで同じパラメーターを使用してテンプレートをインスタンス化すると、同じ定義が複数得られ、コンパイラーはそれらをすべて(1つを除く)削除してエラーを回避します。どうやって使うのextern template?クラスだけに使用できますか、それとも関数にも使用できますか?

また、図1と図2は、テンプレートが単一のヘッダーファイルにあるソリューションに拡張できます。その場合、extern template複数の同じインスタンス化を回避するためにキーワードを使用する必要があります。これはクラスまたは関数のみに適用されますか?


3
これはexternテンプレートの正しい使用法ではありません...これもコンパイルされません
Dani

(1つ)の質問をより明確に表現するために、少し時間をかけてください。コードを投稿する目的は何ですか?それに関連する質問はありません。また、extern template class foo<int>();間違いのようです。
sehe

@Dani>私のビジュアルスタジオ2010では警告メッセージを除いて問題なくコンパイルされます。警告1警告C4231:非標準の拡張が使用されています:テンプレートの明示的なインスタンス化の前に 'extern'
codekiddy

2
@seheの質問は非常に単純です。どのように、いつexternテンプレートキーワードを使用するのですか。(externテンプレートはC ++ 0xの新しい未来です) "また、externテンプレートクラスfoo <int>();は間違いのようです。" いいえ、そうではありません。新しいC ++の本があり、それは私の本の例です。
codekiddy

1
@codekiddy:その後、ビジュアルスタジオは本当に愚かです.. 2番目のバージョンでは、プロトタイプが実装と一致していません。修正しても()、外部行の近くに「予期される非修飾ID」と表示されています。あなたの本とビジュアルスタジオの両方が間違っています。g++やclangなどのより標準に準拠したコンパイラを使用してみてください。
ダニ

回答:


181

知っている場合にのみ、テンプレートをインスタンス化extern templateないようコンパイラーに強制するために使用する必要があります別の場所でインスタンス化することがている。コンパイル時間とオブジェクトファイルサイズを減らすために使用されます。

例えば:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

これにより、次のオブジェクトファイルが生成されます。

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

両方のファイルがリンクされている場合、一方void ReallyBigFunction<int>()が破棄され、コンパイル時間とオブジェクトファイルサイズが無駄になります。

コンパイル時間とオブジェクトファイルサイズを無駄にしexternないために、コンパイラがテンプレート関数をコンパイルしないようにするキーワードがあります。あなたはこれを使用する必要がある場合は、あなたが知っている場合にのみ、それはどこか別の場所に同じバイナリに使用されています。

変更source2.cpp

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

次のオブジェクトファイルになります。

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

これらの両方がリンクされると、2番目のオブジェクトファイルは最初のオブジェクトファイルのシンボルを使用します。破棄する必要はなく、コンパイル時間とオブジェクトファイルサイズも無駄になりません。

これは、テンプレートをvector<int>複数回使用する場合など、プロジェクト内でのみ使用する必要externがあります。1つを除いてすべてのソースファイルで使用する必要があります。

これは、クラスと関数が1つとして、またテンプレートメンバー関数にも適用されます。


2
@codekiddy:Visual Studioがそれによって何を意味するのか私にはわかりません。ほとんどのc ++ 11コードを正しく機能させる場合は、より準拠したコンパイラを使用する必要があります。
ダニ

4
@ダニ:これまでに読んだ外部テンプレートの最も良い説明!
Pietro

90
「別の場所で同じバイナリで使用されていることがわかっている場合。」それは十分でも必要でもありません。コードは「形式が正しくないため、診断は必要ありません」。別のTUの暗黙的なインスタンス化に依存することは許可されていません(コンパイラーは、インライン関数のように、TUを最適化することを許可されています)。別のTUで明示的なインスタンス化を提供する必要があります。
Johannes Schaub-litb 2013

32
私はこの答えがおそらく間違っていることを指摘したいと思います、そして私はそれに噛まれました。幸いなことに、ヨハネスのコメントには多数の賛成票があり、今回はもっと注目しました。この質問の投票者の大多数が実際にこれらのタイプのテンプレートを複数のコンパイルユニットに実装していなかったとしか思えない(今日のように)...少なくともclangの場合、これらのテンプレート定義をあなたのヘッダー!警告してください!
Steven Lu

6
@ JohannesSchaub-litb、もう少し詳しく説明してもらえますか?あなたの反対を完全に理解したかどうかはわかりません。
andreee 2017

48

ウィキペディアには最も良い説明があります

C ++ 03では、翻訳単位で完全に指定されたテンプレートが検出されると、コンパイラーはテンプレートをインスタンス化する必要があります。テンプレートが多くの翻訳単位で同じタイプでインスタンス化される場合、これによりコンパイル時間が劇的に増加する可能性があります。C ++ 03ではこれを防ぐ方法はないため、C ++ 11ではexternデータ宣言に類似したexternテンプレート宣言が導入されました。

C ++ 03には、コンパイラーにテンプレートをインスタンス化するように強制する次の構文があります。

  template class std::vector<MyClass>;

C ++ 11はこの構文を提供します:

  extern template class std::vector<MyClass>;

これは、この翻訳単位でテンプレートをインスタンス化しないようコンパイラーに指示します。

警告: nonstandard extension used...

Microsoft VC ++は、この機能の非標準バージョンを数年前から使用していた(C ++ 03)。コンパイラーは、異なるコンパイラーでもコンパイルする必要があるコードの移植性の問題を防ぐために、警告を出します。

リンク先のページのサンプルを見て、ほぼ同じように機能することを確認してください。もちろん、他の非標準のコンパイラー拡張機能を同時に使用する場合を除いて、MSVCの将来のバージョンでメッセージがなくなることを期待できます。


返信seheのtnx、つまり、これが実際に意味することは、「外部テンプレート」の将来はVS 2010で完全に機能し、警告を無視できるということです。(プラグマを使用してメッセージを無視するなど)、VSC ++でテンプレートが時間どおりにインスタンス化されないようにします。コンパイラ。ありがとう。
codekiddy

4
「... この翻訳単位でテンプレートをインスタンス化しないようコンパイラーに指示します。」これは本当だとは思いません。クラス定義で定義されたメソッドはすべてインラインとしてカウントされるため、STL実装がインラインメソッドを使用する場合std::vector(すべてのメソッドがそうであると確信しています)externは効果がありません。
Andreas Haferburg、2015

うん、この答えは誤解を招くものです。MSFTドキュメント:「特殊化のexternキーワードは、クラスの本体の外部で定義されたメンバー関数にのみ適用されます。クラス宣言の内部で定義された関数は、インライン関数と見なされ、常にインスタンス化されます。」VSのすべてのSTLクラス(最後にチェックされたのは2017年)には、残念ながらインラインメソッドしかありません。
0kcats 2017年

それは、どこで発生するかに関係なく、すべてのインライン宣言に当てはまります。常に@ 0kcats
sehe

@sehe std :: vectorの例を含むWikiへの参照と同じ回答でのMSVCへの参照は、これまでのところありませんが、MSVCでextern std :: vectorを使用することにはいくつかの利点があると信じています。これが標準の要件であるかどうかは不明ですが、他のコンパイラにも同じ問題がある可能性があります。
0kcats 2017年

7

extern template テンプレート宣言が完了している場合にのみ必要です

これは他の回答でも示唆されていましたが、十分に強調されていなかったと思います。

これが意味することは、OPの例ではextern template、ヘッダーのテンプレート定義が不完全だったため、は効果がないということです。

  • void f();:宣言のみ、本文なし
  • class foo:メソッドを宣言しますf()が、定義はありません

したがってextern template、その特定のケースでは定義を削除することをお勧めします。クラスが完全に定義されている場合にのみ、定義を追加する必要があります。

例えば:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

シンボルをコンパイルして表示するnm

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

出力:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

それから、それが未定義であるman nmことをU意味するので、定義はTemplCpp希望どおりにとどまっただけです。

これらすべては、完全なヘッダー宣言のトレードオフに要約されます。

  • 利点:
    • 外部コードが新しいタイプのテンプレートを使用できるようにします
    • オブジェクトの膨張で問題がなければ、明示的なインスタンス化を追加しないオプションがあります
  • 欠点:
    • そのクラスを開発するとき、ヘッダー実装の変更により、スマートビルドシステムがすべてのインクルーダーを再構築します。
    • オブジェクトファイルの肥大化を避けたい場合は、明示的なインスタンス化(不完全なヘッダー宣言の場合と同じ)を行うだけでなくextern template、プログラマーが忘れる可能性があるすべてのインクルーダーを追加する必要があります。

それらのさらなる例は以下に示されています:明示的なテンプレートのインスタンス化-それはいつ使用されますか?

大規模なプロジェクトではコンパイル時間が非常に重要であるため、外部の当事者が独自の複雑なカスタムクラスでコードを再利用する必要がない限り、不完全なテンプレート宣言を強くお勧めします。

その場合は、まずビルド時間の問題を回避するためにポリモーフィズムを使用し、顕著なパフォーマンスの向上が得られる場合にのみテンプレートを使用します。

Ubuntu 18.04でテスト済み。


4

テンプレートに関する既知の問題はコードの肥大化です。これは、クラステンプレートの特殊化を呼び出すすべてのモジュールでクラス定義を生成した結果です。これを防ぐには、C ++ 0xから始めて、クラステンプレートの特殊化の前にキーワードexternを使用します。

#include <MyClass>
extern template class CMyClass<int>;

テンプレートクラスの明示的なインスタンス化は、単一の翻訳単位でのみ発生する必要があります。できれば、テンプレート定義を含むもの(MyClass.cpp)を使用してください。

template class CMyClass<int>;
template class CMyClass<float>;

0

以前に関数にexternを使用したことがある場合、テンプレートについてもまったく同じ哲学に従います。そうでない場合は、単純な関数のexternを使用すると役立つ場合があります。また、ヘッダーファイルにexternを入れて、必要なときにヘッダーを含めることもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.