1

静的メンバー変数を含む C++ テンプレート クラスについて、および動的ライブラリまたは共有オブジェクトからそれらをエクスポートする方法について、多くの質問がありました。複数の共有オブジェクトがあり、それぞれが独自のインスタンス化セットを持っているが、別の共有オブジェクトからのインスタンス化を使用している可能性がある場合はどうすればよいでしょうか?

次のコード例を検討してください。

/* file: common.h */

#include <stdio.h>
#define PRINT fprintf (stderr, "(template %d) %d -> %d\n", parameter, data, new_data)

template <int parameter>
class SharedClass
{
    static int data;
public:
    static void Set(int new_data) { PRINT; data = new_data; }
};

template <int parameter>
int SharedClass<parameter>::data = parameter;


/* file: library1.h */

extern template class SharedClass<1>;
void Library1Function();


/* file: library1.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

template class SharedClass<1>;

void Library1Function()
{
    SharedClass<1>::Set (100);
    SharedClass<2>::Set (200);
}


/* file: library2.h */

extern template class SharedClass<2>;
void Library2Function();


/* file: library2.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

template class SharedClass<2>;

void Library2Function()
{
    SharedClass<1>::Set (1000);
    SharedClass<2>::Set (2000);
}


/* file: main.cpp */

#include "common.h"
#include "library1.h"
#include "library2.h"

int main()
{
    Library1Function();
    Library2Function();
    SharedClass<1>::Set (-1);
    SharedClass<2>::Set (-2);
}

次に、GCC を使用して 2 つのライブラリとアプリケーションをビルドするとします。

$ g++ -fPIC -fvisibility=default -shared library1.cpp -o lib1.so
$ g++ -fPIC -fvisibility=default -shared library2.cpp -o lib2.so
$ g++ -fvisibility=default main.cpp -o main -Wl,-rpath=. -L. -l1 -l2

そして、実行可能ファイルを実行すると、次の結果が得られます。

$ ./main
(template 1) 1 -> 100
(template 2) 2 -> 200
(template 1) 100 -> 1000
(template 2) 200 -> 2000
(template 1) 1000 -> -1
(template 2) 2000 -> -2

これは、ライブラリと実行可能ファイルの両方が、同じテンプレートごとの静的ストレージにアクセスすることを意味します。
バイナリで「nm -C」を実行すると、各静的メンバーが対応するライブラリで 1 回だけ定義されていることがわかります。

$ nm -C -A *.so main | grep ::data
lib1.so:0000000000001c30 u SharedClass<1>::data
lib2.so:0000000000001c30 u SharedClass<2>::data

しかし、いくつか質問があります。

  1. 両方のヘッダーからを削除するextern template class ...と、静的メンバーが各バイナリに存在することがわかりますが、テスト アプリケーションは引き続き適切に動作するのはなぜでしょうか?

    $ nm -C -A *.so main | grep ::data
    lib1.so:0000000000001c90 u SharedClass<1>::data
    lib1.so:0000000000001c94 u SharedClass<2>::data
    lib2.so:0000000000001c94 u SharedClass<1>::data
    lib2.so:0000000000001c90 u SharedClass<2>::data
    main:0000000000401e48 u SharedClass<1>::data
    main:0000000000401e4c u SharedClass<2>::data
    
  2. これを MSVC でビルドすることは可能ですか? または、より具体的には、一部のインスタンス化をエクスポートし、一部をインポートする方法と
    対処方法は?__declspec(dllexport)__declspec(dllimport)

  3. 最後に、これは未定義の動作の例ですか?

4

1 に答える 1

1

ポイント 1 への回答: ダイナミック リンカーがシンボルを解決するとき、リンク先のモジュールのリストを使用します。最初にロードされたモジュールが最初にチェックされ、次に 2 番目、というようにチェックされます。

IIRC では、dataメンバーが main、lib1.so、および lib2.so で使用されている場合、メンバーが同じモジュールで宣言されていても、動的シンボル参照として扱われます。したがって、プログラムの実行時にリンカーがシンボルを解決しようとすると、3 つのモジュールすべてdataが、3 つのモジュールのうちの 1 つのみ (最初にロードされた方) のメンバー実装を使用することになります。他の 2 つのペアはまだメモリにロードされていますが、使用されていません。

( std::cout << &(SharedClass<n>::data) << std::endl3 つのモジュールすべてで試してみてください。出力されるアドレスは、6 つのケースすべてで同じでなければなりません。)

ポイント3に答えるために、私はこの動作が未定義であるとはまったく考えていません。正確に何が起こるかは、システムの動的リンカーによって異なりますが、この状況をまったく同じ方法で処理しないリンカーは知りません。

MSVC の経験があまりないので、ポイント 2 について話すことはできません。

于 2012-06-11T20:14:59.377 に答える