33

我已经看到了单例模式的实现,其中实例变量在 GetInstance 方法中被声明为静态变量。像这样:

SomeBaseClass &SomeClass::GetInstance()
{
   static SomeClass instance;
   return instance;
}

我看到了这种方法的以下积极方面:

  • 代码更简单,因为只有在第一次调用 GetInstance 时,编译器才负责创建这个对象。
  • 代码更安全,因为没有其他方法可以获取对实例的引用,而是使用 GetInstance 方法,并且没有其他方法可以更改实例,而是在 GetInstance 方法中。

这种方法有哪些负面影响(除了这不是非常面向 OOP 的)?这是线程安全的吗?

4

4 回答 4

44

在 C++11 中它是线程安全的:

§6.7 [stmt.dcl] p4 如果在初始化变量时控制同时进入声明,则并发执行应等待初始化完成。

在 C++03 中:

  • 在 g++ 下它是线程安全的。
    但这是因为 g++ 显式添加了代码来保证它。

一个问题是,如果您有两个单例,并且它们在构造和销毁过程中尝试相互使用。

阅读本文: 查找 C++ 静态初始化顺序问题

这个问题的一个变种是如果从全局变量的析构函数访问单例。在这种情况下,单例肯定已被销毁,但 get 方法仍将返回对已销毁对象的引用。

有一些方法可以解决这个问题,但它们很混乱,不值得做。只是不要从全局变量的析构函数中访问单例。

更安全但丑陋的定义:
我相信您可以添加一些适当的宏来整理它

SomeBaseClass &SomeClass::GetInstance()
{
#ifdef _WIN32 
Start Critical Section Here
#elif  defined(__GNUC__) && (__GNUC__ > 3)
// You are OK
#else
#error Add Critical Section for your platform
#endif

    static SomeClass instance;

#ifdef _WIN32
END Critical Section Here
#endif 

    return instance;
}
于 2009-01-16T08:20:33.037 回答
5

如图所示,它不是线程安全的。C++ 语言对线程保持沉默,因此您无法从该语言获得内在保证。您将不得不使用平台同步原语,例如 Win32 ::EnterCriticalSection(),来保护访问。

您的特定方法将是有问题的 b/c 编译器将插入一些(非线程安全)代码以instance在第一次调用时初始化静态,很可能是在函数体开始执行之前(因此在可以调用任何同步之前)。 )

使用全局/静态成员指针SomeClass,然后在同步块中初始化将证明实现起来的问题较小。

#include <boost/shared_ptr.hpp>

namespace
{
  //Could be implemented as private member of SomeClass instead..
  boost::shared_ptr<SomeClass> g_instance;
}

SomeBaseClass &SomeClass::GetInstance()
{
   //Synchronize me e.g. ::EnterCriticalSection()
   if(g_instance == NULL)
     g_instance = boost::shared_ptr<SomeClass>(new SomeClass());
   //Unsynchronize me e.g. :::LeaveCriticalSection();
   return *g_instance;
}

我没有编译这个,所以它只是为了说明目的。它还依赖于 boost 库来获得与原始示例相同的生命周期(或大约相同的生命周期)。您还可以使用 std::tr1 (C++0x)。

于 2009-01-16T06:32:39.213 回答
1

根据规范,这也应该在 VC++ 中工作。有谁知道有没有?

只需添加关键字 volatile。如果 msdn 上的文档正确,则 Visual c++ 编译器应生成互斥锁。

SomeBaseClass &SomeClass::GetInstance()
{
   static volatile SomeClass instance;
   return instance;
}
于 2010-08-18T15:25:31.520 回答
-11

它分享了 Singleton 实现的所有常见缺陷,即:

  • 这是无法测试的
  • 它不是线程安全的(这很简单,看看你是否想象两个线程同时进入函数)
  • 这是内存泄漏

我建议不要在任何生产代码中使用 Singleton。

于 2009-01-16T04:07:43.720 回答