3

在尝试使用 Clang 编译现有的(由 GCC 开发的)代码库时,我们面临着这个有趣的问题。结果,Clang 编译的可执行文件创建了一些单例的多个实例。不确定我们的使用和理解是否符合标准,或者 GCC 和/或 Clang 或 Linux 上的 C++ 标准库和工具链是否真的有问题。

  • 我们正在使用工厂来创建单例实例
  • 实际创建委托给策略模板
  • 在某些地方,我们使用单例工厂的变体,其中可以在定义站点配置单例的实际类型,而无需向访问单例的客户端显示具体类型。客户端只知道接口类型
  • 通过从不同编译单元使用的内联函数引用“相同”静态变量时出现问题

以下是摘录,省略了任何锁定、生命周期问题、初始化和清理


文件 1clang-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

文件 2clang-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;
  }

文件 3clang-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

值得注意的点

  • 我们只使用了一个接入点实例
  • 然而,使用(内联)函数的不同编译单元Holder<T,F>::get()将看到静态变量的不同位置instance
  • 虽然实际的 ctor 调用ConfigurableHolder是使用要创建的单例的具体类型模板化的,但此特定类型信息被删除;它不应该与类型AdapterConfigurableHolder
  • 如果这种理解是正确的,那么所有的用法都get()应该看到相同类型的Holder,因此嵌入的静态变量的位置相同Holder
  • 但实际上,Clang 编译的可执行文件再次调用工厂 for sub2,这是从另一个编译单元调用的,而sub1sub3共享相同的单例实例,如预期

有趣的是,使用 Clang-3.0 构建的可执行文件的符号表显示此静态变量已链接两次(使用 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 64bit (GCC-4.7 and Clang-3.0) 和 Debian/testing 32bit (Clang-3.2) 来构建

4

1 回答 1

0

解决方法是声明您的单例模板类 extern,并在单个编译单元中显式实例化单例。

如果您的编译单元位于单独的(共享)库中,那么 Clang 的行为方式仅仅是因为它可以。

编译代码时,编译器会在每次完全指定单例模板时对其进行实例化。在链接时,除了一个实例之外的所有实例都被丢弃。但是,如果您的项目中有共享库,并且有多个链接时间,会发生什么?每个共享对象都有一个模板实例。GCC 确保最终可执行文件中只有一个幸存的模板实例化(可能使用模糊链接?),但显然 Clang 没有。

于 2014-06-02T10:57:39.727 回答