既存の (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()
同じ型、したがって同じ場所を参照する必要があります。Holder
Holder
- しかし実際には、Clang でコンパイルされた実行可能ファイルは、別の
sub2
コンパイル ユニットから呼び出される のファクトリを再度呼び出しますが、期待どおりに同じシングルトン インスタンスを共有しますsub1
。sub3
興味深いことに、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) を使用しています。