单身死亡参考问题

时间:2010-08-18 07:15:38

标签: c++ multithreading singleton

我正在阅读关于单身人士的很多内容。我正在考虑单身人士之间的死亡参考问题。在网上的每个引子中,当一个单例在其析构函数中调用其他单例时遇到这个问题,并且该单例已经被破坏,比如可以从许多其他单例的析构函数中调用Log单例。

我无法想象在其他情况下(除了在dtr中引用其他单例),死引用将是一个问题。你能给我一个存在这样一个问题的真实世界的例子,我该如何解决它?

问题是我需要在我们的项目中实现几个单身人士,这些单身人士彼此沟通,而我却很难选择正确的方式。请不要说不要使用单身人士,因为那不是我的决定。

6 个答案:

答案 0 :(得分:3)

破坏顺序问题是单身模式的一部分。

  

请不要说不要使用   单身,因为那不是我的   决定。

不使用它们是正确的做法 - 因为这是不可能的,你将不得不使用hacky解决方法。以下是一些可能的解决方案,但它们都不是很漂亮:

  • 不要在你的析构函数中引用其他单身人士
  • 在主
  • 结束时以正确的顺序明确摧毁单身人士
  • 让你的单身人士通过weak_ptr保持对其他单身人士的引用 - 他们可以彼此独立地被摧毁,你可以在使用之前安全地检查引用的单身人士是否仍然存在

另外,我建议不要在多线程上下文中创建或销毁单例 - 更容易确保在任何新线程之前创建所有单例,并且除了主线程之外的所有线程在销毁之前都已停止。

答案 1 :(得分:1)

DumbCoder已经指出了你正确的方向。在Modern C ++设计中,Andrei Alexandrescu解释了Singletons的复杂设计问题,并根据对单例的精确要求展示了多种解决方案。

但它并不是所有可能的单例实现的完整指南。您应该阅读它不是为了代码,而是为了理解分析。将您获得的知识应用到您的特定情况中。

要回答您的具体问题,“死”引用的另一个常见情况更好地称为“未出生的引用” - 在构造函数运行之前使用单例。但显而易见的是,由于单身人士在节目的大部分生命周期中都存在,因此他们不存在的唯一两次是在开始和结束时。

答案 2 :(得分:1)

我上面没有提到过的一种可能性,根据他们管理的内容可能会或者可能不会被接受:在堆上分配单例并且不要破坏它们......让OS回收任何描述符/应用程序终止时它们持有的内存/锁等等(注意:不适用于所有内容,例如锁定在共享内存中)。

答案 3 :(得分:1)

从这里复制:Finding C++ static initialization order problems(没有人会跟着一个链接抱歉)

另见本文:C++ Singleton design pattern

销毁问题:

在销毁对象后存在访问对象的潜在问题。只有从另一个全局变量的析构函数访问对象时才会发生这种情况(通过全局我引用任何非本地静态变量)。

解决方案您必须确保强制销毁命令 请记住,破坏的顺序与构造顺序完全相反。因此,如果您在析构函数中访问该对象,则必须保证该对象尚未被销毁。要做到这一点,你必须保证在构造调用对象之前完全构造对象。

class B
{
    public:
        static B& getInstance_Bglob;
        {
            static B instance_Bglob;
            return instance_Bglob;;
        }

        ~B()
        {
             A::getInstance_abc().doSomthing();
             // The object abc is accessed from the destructor.
             // Potential problem.
             // You must guarantee that abc is destroyed after this object.
             // To gurantee this you must make sure it is constructed first.
             // To do this just access the object from the constructor.
        }

        B()
        {
            A::getInstance_abc();
            // abc is now fully constructed.
            // This means it was constructed before this object.
            // This means it will be destroyed after this object.
            // This means it is safe to use from the destructor.
        }
};

答案 4 :(得分:0)

据我记忆,单身人士是按照你第一次调用访问函数的顺序创建的,并以相反的顺序销毁。

因此,您可以为您的应用程序创建一个init函数(并确保它是您在main函数中调用的第一个函数)。

在此函数中,按照您希望它们创建的顺序调用单例访问函数。

答案 5 :(得分:0)

我们缺乏信息,最值得注意的是我希望我们正在谈论C ++ 0x否则它将会非常困难。

第一个解决方案是明确管理你的单身人士。您在网络上遇到的大多数设计都注重简单性和易用性,但在一般情况下会以正确性为代价。

最简单的方法是不要让你的单身人士相互依赖是实例化它们并在你仍然是单线程的时候释放它们(以避免同步问题)并且顺序正确。

这自然是一个Singleton Manager的想法,它是某种“超级单身人士”,并将实例化你的单身并相应地释放它们。对单例的所有访问都是通过它完成的,这样它就可以确保它们在访问时是活的。再次,在单线程情况下发生和破坏。

当我们谈论延迟初始化(按需)时会变得更加困难。最简单的方案是本地静态变量:

MySingleton& Get() { static MySingleton M; return M; }

C ++ 0x最终保证只会实例化MySingleton的一个实例,这会让事情变得更容易!但是你确实遇到了“死参考”问题。

在C ++中,静态对象的破坏顺序只是构造顺序的反向,因此一种解决方案是强制在构造函数(所有这些)中使用单个对象的析构函数中使用的任何单例。这样你实际上可以保证它将在之前构建,并因此在之后被破坏。

请注意,在C ++ 03(或更早)的多线程环境中,延迟实例化很困难,因为无法保证会创建单个实例......并且在此时获取锁定非常困难(之后)总而言之,互斥体本身就是一个单身......不是吗?)。