G ++错误地破坏了静态变量

Yuv*_*lon 10 c++ static templates

我有以下类尝试实现通用的Singleton.

struct BaseObject
{
   virtual ~BaseObject() {}
};
class _helper
{
private:
    template<typename T> friend class Singleton;
    set<BaseObject*> _s;
    static _helper& _get()
    {
        static _helper t;
        return t;
    }
    _helper()
    {
        cout<<" _helper ctor"<<endl;
    }
    ~_helper()
    {
        cout<<" _helper dtor"<<endl;
        //assert(_s.empty());
    }
};

// Singleton<foo>::Instance() returns a unique instance of foo
template <typename T>
class Singleton : virtual private T
{
public:
    static T& Instance()
    {
        static Singleton<T> _T;
        return _T;
    } 

private:
    Singleton()
    {
        cout<<"inserting into helper "<<typeid(T).name()<<" ptr "<<this<<endl;
        assert(!_helper::_get()._s.count(this));
        _helper::_get()._s.insert(this);
    }
    ~Singleton()
    {
        cout<<"erasing from helper "<<typeid(T).name()<<" ptr "<<this<<endl;
        assert(_helper::_get()._s.count(this));
        _helper::_get()._s.erase(this);
    }
};
Run Code Online (Sandbox Code Playgroud)

现在,如果我Singleton< bar>::Instance()跟随后调用Singleton< foo>::Instance(),我应该看到以下输出:

 inserting into helper 3bar ptr 0x509630  
 _helper ctor  
 inserting into helper 3foo ptr 0x509588  
 erasing from helper 3foo ptr 0x509588  
 erasing from helper 3bar ptr 0x509630  
 _helper dtor  
Run Code Online (Sandbox Code Playgroud)

但是在某些情况下,我看到以下内容:

 inserting into helper 3bar ptr 0x509630  
 _helper ctor  
 inserting into helper 3foo ptr 0x509588  
 erasing from helper 3bar ptr 0x509630  
 _helper dtor  
 erasing from helper 3foo ptr 0x509588  
Run Code Online (Sandbox Code Playgroud)

请注意,在第二种情况下,barfoo按照构造它们的顺序进行破坏.当单个foobar单例在共享库(.so)中作为静态引用实例化时,似乎会发生这种情况:

static bar& b = Singleton<bar>::Instance();   
static foo& f = Singleton<foo>::Instance();   
Run Code Online (Sandbox Code Playgroud)

任何想法为什么会这样做?

n. *_* m. 1

如果单例和助手位于不同的翻译单元或不同的共享对象中,则可能会发生这种情况。请记住,预测模板实例最终将在哪个翻译单元中是相当困难的。还要记住,每个共享对象都可以获得自己的 say 实例Singleton<foo>::_T。所以你有某种基于共享对象的单例(恕我直言,不是很有用)。

请注意,您的助手在最后一个对象被删除之前就被销毁了。这将导致程序退出时崩溃。是的,这件事确实发生在我身上。您需要在 _helper 类中实现一个对象计数器,以便在至少有一个对象向其注册之前不会被销毁。或者,分配堆上的所有单例,并让助手在其生命周期结束时销毁它们。

update如果两个静态对象属于同一个动态库,则这种破坏顺序反转可能不会发生。否则它肯定会而且确实会发生。这里建议程序员不要跨动态库边界导出静态对象。

  • 即使在那种情况下也不应该发生。跨 TU 边界的构造顺序未定义,但标准要求销毁顺序是完成适当构造函数(或动态初始化)的相反顺序。也就是说,给定两个翻译单元中的两个静态对象“a”和“b”,标准需要两种可能的顺序之一:“a,b,~b,~a”或“b,a,~a,~b” ` (2认同)
  • 该标准没有提及动态加载的库。实际上,当卸载这样的库时,它“拥有”的所有静态对象都会被销毁,无论其他 .so/.dll 中的静态对象是如何构造的。 (2认同)