3

この合成例を考えてみましょう。Visual Studio 2010 ソリューションに 2 つのネイティブ C++ プロジェクトがあります。1 つはコンソール exe で、もう 1 つは lib です。

lib には 2 つのファイルがあります。

// TImage.h

template<class T> class TImage
{
public:
  TImage()
  {
#ifndef _LIB
    std::cout << "Created (main), ";
#else
    std::cout << "Created (lib), ";
#endif
    std::cout << sizeof(TImage<T>) << std::endl;
  }

#ifdef _LIB
  T c[10];
#endif
};

void CreateImageChar();
void CreateImageInt();

// TImage.cpp

void CreateImageChar()
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;
}
void CreateImageInt()
{
  TImage<int> image;
  std::cout << sizeof(TImage<int>) << std::endl;
}

そしてexeの1つのファイル:

// main.cpp

int _tmain(int argc, _TCHAR* argv[])
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;

  CreateImageChar();
  CreateImageInt();

  return 0;
}

私は実際にこのようにするべきではありませんが、これは何が起こっているのかを理解するためのものです. そして、それは、何が起こるかです:

// cout:
Created (main), 1
1
Created (main), 1
10
Created (lib), 40
40

リンカは lib のバージョンのTImage<char>を exe のバージョンの でオーバーライドしTImage<char>ます。しかし、exe のバージョンの がないためTImage<int>、lib のバージョンのTImage<int>?.. が保持されます。

更新:以下の効果の説明は正しいです、ありがとう。しかし、問題は「これがどのように起こったのか」でした.. 「複数定義されたシンボル」のようなリンカーエラーが発生することを期待していました。したがって、最も適切な回答はAntonio Pérez の回答からのものです。

4

6 に答える 6

2

テンプレート コードは、複製されたオブジェクト コードを作成します。

コンパイラは、テンプレートをインスタンス化するときに、指定した型のテンプレート コードをコピーします。したがって、TImage.cppをコンパイルすると、テンプレートの 2 つのバージョンのオブジェクト コードが得られます。1 つはchar用で、もう 1 つはint用ですTImage.o。次にがコンパイルされ、 charmain.cppの新しいバージョンのテンプレートが に取得されます。次に、リンカは、alwaysのものをたまたま使用します。main.omain.o

これは、出力が「Created」行を生成する理由を説明しています。しかし、オブジェクトのサイズに関する 3 行目と 4 行目の不一致を確認するのは少し不可解でした。

Created (main), 1
10

これは、コンパイラがsizeofコンパイル時に演算子を解決するためです。

于 2011-07-29T07:24:07.533 に答える
1

__decelspec(dllexport)コードにorがないため、静的ライブラリを構築していると仮定していextern "C"ます。ここで何が起こるかは次のとおりです。コンパイラは、libのTImage<char>とのインスタンスを作成します。TImage<int>また、実行可能ファイルのインスタンスも作成します。リンカーがスタティック ライブラリと実行可能ファイルのオブジェクトを結合すると、重複するコードが削除されます。ここで、スタティック ライブラリはオブジェクト コードと同様にリンクされているため、1 つの大きな実行可能ファイルを作成しても、複数のスタティック ライブラリと 1 つの実行可能ファイルを作成しても、違いはありません。1 つの実行可能ファイルをビルドする場合、結果はオブジェクトがリンクされている順序に依存します。別名「未定義」。

ライブラリを DLL に変更すると、動作が変わります。DLL の境界を越えて呼び出しているため、それぞれに のコピーが必要ですTImage<char>。ほとんどの場合、DLL は、ライブラリが機能することを期待するのと同じように動作します。通常、静的ライブラリは便利なだけなので、コードをプロジェクトに入れる必要はありません。

注: これは Windows にのみ適用されます。POSIX システムでは、*.a ファイルは *.so ファイルのように動作するため、コンパイラの開発者は頭が痛くなります。

編集: DLL 境界を越えて TImage クラスを決して渡さないでください。それはクラッシュを確実にします。これは、デバッグ ビルドとリリース ビルドを混在させると Microsoft の std::string 実装がクラッシュするのと同じ理由です。これらは、NDEBUG マクロでのみ行ったこととまったく同じことを行います。

于 2011-07-29T08:06:36.003 に答える
0

ライブラリ内には、TImage構築時に「lib」を出力する があり、 の配列が含まれていますT。これらは 2 つあり、1 つは 用int、もう 1 つは 用charです。

main には、TImage構築時に「main」を出力する があり、 の配列が含まれていませんTcharこの場合はバージョンのみです。intバージョンの作成を要求することは決してないからです。

リンクすると、リンカーは 2 つのTImage<char>コンストラクターのいずれかを公式のコンストラクターとして選択します。たまたまmainのバージョンを選択しました。これが、3 行目に「lib」ではなく「main」と表示される理由です。そのバージョンのコンストラクターを呼び出しているためです。一般に、呼び出されるコンストラクターのバージョンは気にしません...それらはすべて同じである必要がありますが、その要件に違反しています。

それが重要な部分です: あなたのコードは壊れています。charライブラリ関数内でその配列が表示されることを期待しますTImage<char> が、コンストラクターはそれを作成しません。 さらに、あなたが言うと想像してくださいnew TImage<char>main、ライブラリ内の関数へのポインタを渡し、そこで削除されます。 main1 バイトのスペースを割り当てると、ライブラリ関数は 10 を解放しようとします。または、CreateImageCharメソッドがTImage<char>作成するを返した場合...mainは、戻り値用にスタックに 1 バイトを割り当て、CreateImageChar10 バイトのデータで埋めます。等々。

于 2011-07-29T08:02:21.583 に答える
0

テンプレートは、コンパイル自体中にクラスの複製 (スペース) を作成します。したがって、使用するテンプレートが多すぎる場合、スマート コンパイラはテンプレートのパラメータ化に基づいてテンプレートを最適化しようとします。

于 2011-07-29T07:45:06.610 に答える
0

メモリ レイアウトはコンパイル時の概念です。リンカーとは関係ありません。この関数は、別のバージョンの TImage でコンパイルされているためmain、TImage が関数よりも小さいと認識しています。CreateImage...

関数がヘッダーでインライン関数として定義されている場合CreateImage...、それらは main.cpp のコンパイル ユニットの一部になるため、レポートと同じサイズ特性がmainレポートされます。

これは、テンプレートやインスタンス化されたときとは関係ありません。TImage が通常のクラスの場合、同じ動作が見られます。

編集: cout の 3 行目に「Created (lib), 10」が含まれていないことに気付きました。タイプミスではないと仮定すると、何が起こっているのかはCreateImageChar、コンストラクターへの呼び出しをインライン化していないため、main.cpp のバージョンを使用していると思われます。

于 2011-07-29T07:21:15.747 に答える
0

テンプレートを使用すると、コンパイラは常にテンプレートをインスタンス化します (定義が利用可能な場合)。

これは、目的の特殊化に必要な関数、メソッドなどを生成し、それらをオブジェクト ファイルに配置することを意味します。これが、使用している特定の特殊化に対して定義を使用可能にする (通常はヘッダー ファイルで) か、既存のインスタンス (たとえば、別のオブジェクト ファイルまたはライブラリで) が必要な理由です。

ここで、リンク時に、通常は許可されない状況が発生する可能性があります: クラス/関数/メソッドごとに複数の定義。テンプレートの場合、これは特に許可されており、コンパイラは 1 つの定義を選択します。それがあなたの場合に起こっていることであり、あなたが「オーバーライド」と呼んでいるものです。

于 2011-07-29T07:21:15.173 に答える