3

既存の (GCC で開発された) コード ベースを Clang でコンパイルしようとすると、この興味深い問題に直面します。その結果、Clang でコンパイルされた実行可能ファイルは、一部のシングルトンの複数のインスタンスを作成します。私たちの使用法と理解が標準に準拠しているかどうか、または Linux の GCC や Clang または C++ 標準ライブラリとツールチェーンに実際に問題があるかどうかはわかりません。

  • ファクトリを使用してシングルトン インスタンスを作成しています
  • 実際の作成はポリシー テンプレートに委任されます
  • いくつかの場所では、シングルトン ファクトリのバリエーションを使用しています。ここでは、シングルトンにアクセスするクライアントにその具象型を明らかにする必要なく、定義サイトでシングルトンの実際の型を構成できます。クライアントはインターフェイスの種類を知っているだけです
  • 異なるコンパイル単位から使用されるインライン関数を介して「同じ」静的変数を参照すると、問題が発生します

以下は、ロック、ライフサイクルの問題、初期化、およびクリーンアップを省略した抜粋です。


ファイル-1 :clang-static-init.hpp

#include <iostream>
using std::cout;

namespace test {

  /* === Layer-1: a singleton factory based on a templated static variable === */

  template<typename I                     ///< Interface of the product type
          ,template <class> class Fac     ///< Policy: actual factory to create the instance
          >
  struct Holder
    {
      static I* instance;

      I&
      get()
        {
          if (!instance)
            {
              cout << "Singleton Factory: invoke Fabrication ---> address of static instance variable: "<<&instance<<"...\n";

              instance = Fac<I>::create();
            }
          return *instance;
        }
    };

  /**
   * allocate storage for the per-type shared
   * (static) variable to hold the singleton instance
   */
  template<typename I
          ,template <class> class F
          >
  I* Holder<I,F>::instance;




  template<typename C>
  struct Factory
    {
      static C*
      create()
        {
          return new C();
        }
    };





  /* === Layer-2: configurable product type === */

  template<typename I>
  struct Adapter
    {
      typedef I* FactoryFunction (void);

      static FactoryFunction* factoryFunction;


      template<typename C>
      static I*
      concreteFactoryFunction()
        {
          return static_cast<I*> (Factory<C>::create());
        }


      template<typename X>
      struct AdaptedConfigurableFactory
        {
          static X*
          create()
            {
              return (*factoryFunction)();
            }
        };
    };

  /** storage for the per-type shared function pointer to the concrete factory */
  template<typename I>
  typename Adapter<I>::FactoryFunction*  Adapter<I>::factoryFunction;



  template<typename C>
  struct TypeInfo { };



  /**
   * Singleton factory with the ability to configure the actual product type C
   * only at the \em definition site. Users get to see only the interface type T
   */
  template<typename T>
  struct ConfigurableHolder
    : Holder<T, Adapter<T>::template AdaptedConfigurableFactory>
    {
      /** define the actual product type */
      template<typename C>
      ConfigurableHolder (TypeInfo<C>)
        {
          Adapter<T>::factoryFunction = &Adapter<T>::template concreteFactoryFunction<C>;
        }
    };





  /* === Actual usage: Test case fabricating Subject instances === */

  struct Subject
    {
      static int creationCount;

      Subject();

    };

  typedef ConfigurableHolder<Subject> AccessPoint;

  /** singleton factory instance */
  extern AccessPoint fab;


  Subject& fabricate();

} // namespace test

ファイル-2 :clang-static-init-1.cpp

#include "clang-static-init.hpp"


test::Subject&
localFunction()
{
  return test::fab.get();
}


int
main (int, char**)
  {
    cout <<  "\nStart Testcase: invoking two instances of the configurable singleton factory...\n\n";

    test::Subject& ref1 = test::fab.get();
    test::Subject& sub2 = test::fabricate();  ///NOTE: invoking get() from within another compilation unit reveales the problem
    test::Subject& sub3 = localFunction();

    cout << "sub1="  << &ref1
         << "\nsub2="<< &sub2
         << "\nsub3="<< &sub3
         << "\n";


    return 0;
  }

ファイル-3 :clang-static-init-2.cpp

#include "clang-static-init.hpp"



namespace test {


  int Subject::creationCount = 0;

  Subject::Subject()
    {
      ++creationCount;
      std::cout << "Subject("<<creationCount<<")\n";
    }



  namespace {
      TypeInfo<Subject> shall_build_a_Subject_instance;
  }

  /**
   * instance of the singleton factory
   * @note especially for this example we're using just \em one
   *       shared instance of the factory.
   *       Yet still, two (inlined) calls to the get() function might
   *       access different addresses for the embedded singleton instance
   */
  AccessPoint fab(shall_build_a_Subject_instance);


  Subject&
  fabricate()
  {
    return fab.get();
  }


} // namespace test

特筆すべき点

  • AccessPoint のインスタンスを 1 つだけ使用しています。
  • それでも、(インライン化された)関数を使用するさまざまなコンパイル単位では、静的変数のさまざまな場所Holder<T,F>::get()が表示されますinstance
  • 実際の ctor 呼び出しConfigurableHolderは、作成するシングルトンの具象型でテンプレート化されますが、この特定の型情報は消去されます。Adapterまたは_ConfigurableHolder
  • この理解が正しければ、 のすべての使用法は、に埋め込まれた静的変数のget()同じ型、したがって同じ場所を参照する必要があります。HolderHolder
  • しかし実際には、Clang でコンパイルされた実行可能ファイルは、別のsub2コンパイル ユニットから呼び出される のファクトリを再度呼び出しますが、期待どおりに同じシングルトン インスタンスを共有しますsub1sub3

興味深いことに、Clang-3.0 でビルドされた実行可能ファイルのシンボル テーブルは、この静的変数が 2 回リンクされていることを示しています (動作は Clang-3.2 を使用する場合と同じです)。

10: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS research/clang-static-init-1.cpp
11: 0000000000400cd0    11 FUNC    LOCAL  DEFAULT   14 global constructors keyed to a
12: 0000000000400b70   114 FUNC    LOCAL  DEFAULT   14 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::get()
13: 00000000004027e0     8 OBJECT  LOCAL  DEFAULT   28 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::instance
14: 00000000004027d8     1 OBJECT  LOCAL  DEFAULT   28 std::__ioinit
15: 0000000000400b10    62 FUNC    LOCAL  DEFAULT   14 __cxx_global_var_init
16: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS research/clang-static-init-2.cpp
17: 00000000004010e8     0 NOTYPE  LOCAL  DEFAULT   17 GCC_except_table9
18: 0000000000400e60    16 FUNC    LOCAL  DEFAULT   14 global constructors keyed to a
19: 00000000004027f9     1 OBJECT  LOCAL  DEFAULT   28 test::(anonymous namespace)::shall_build_a_Subject_instance
20: 0000000000400de0   114 FUNC    LOCAL  DEFAULT   14 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::get()
21: 0000000000402800     8 OBJECT  LOCAL  DEFAULT   28 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::instance

...GCC-4.7.2 でコンパイルされた実行可能ファイルの関連セクションが期待どおりに読み取られる間

44: 0000000000400b8c    16 FUNC    GLOBAL DEFAULT   14 localFunction()
45: 00000000004026dc     1 OBJECT  GLOBAL DEFAULT   28 test::fab
46: 0000000000400c96    86 FUNC    WEAK   DEFAULT   14 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::get()
47: 00000000004026e0   272 OBJECT  GLOBAL DEFAULT   28 std::cout
48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(st
49: 0000000000400d4b    16 FUNC    GLOBAL DEFAULT   14 test::fabricate()
50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
51: 00000000004026d0     8 OBJECT  UNIQUE DEFAULT   28 test::Holder<test::Subject, test::Adapter<test::Subject>::AdaptedConfigurableFactory>::instance
52: 0000000000400cec    15 FUNC    WEAK   DEFAULT   14 test::Adapter<test::Subject>::AdaptedConfigurableFactory<test::Subject>::create()
53: 00000000004026c8     8 OBJECT  UNIQUE DEFAULT   28 test::Adapter<test::Subject>::factoryFunction

ビルドには Debian/stable 64 ビット (GCC-4.7 および Clang-3.0) と Debian/testing 32 ビット (Clang-3.2) を使用しています。

4

1 に答える 1

0

修正は、シングルトン テンプレート クラス extern を宣言し、単一のコンパイル ユニットでシングルトンを明示的にインスタンス化することです。

コンパイル ユニットが個別の (共有) ライブラリにある場合、Clang は単純にこのように動作します。

コードがコンパイルされると、コンパイラはシングルトン テンプレートが完全に指定されるたびにインスタンス化します。リンク時に、1 つを除くすべてのインスタンス化が破棄されます。しかし、プロジェクトに共有ライブラリがあり、複数のリンクタイムがある場合はどうなるでしょうか? 各共有オブジェクトには、テンプレートのインスタンスが 1 つあります。GCC は、最終的な実行可能ファイルに生き残ったテンプレートのインスタンス化が 1 つだけであることを保証します (おそらくあいまいなリンケージを使用しますか?) が、明らかに Clang はそうではありません。

于 2014-06-02T10:57:39.727 に答える