54

我已经阅读了很多关于 Singletons 的内容,它们应该在什么时候使用,什么时候不应该使用,以及如何安全地实现它们。我正在用 C++11 编写,并且遇到了 Meyer 的单例延迟初始化实现,如本问题所示。

这个实现是:

static Singleton& instance()
{
     static Singleton s;
     return s;
}

我从 SO 上的其他问题中理解这是线程安全的,但我不明白这实际上是一个单例模式。我已经用其他语言实现了单例,这些总是像维基百科中的这个例子一样结束:

public class SingletonDemo {
        private static volatile SingletonDemo instance = null;

        private SingletonDemo() {       }

        public static SingletonDemo getInstance() {
                if (instance == null) {
                        synchronized (SingletonDemo .class){
                                if (instance == null) {
                                        instance = new SingletonDemo ();
                                }
                      }
                }
                return instance;
        }
}

当我看第二个例子时,它是一个单例是非常直观的,因为该类持有对它自己的一个实例的引用,并且只返回那个实例。但是,在第一个示例中,我不明白这如何阻止对象的两个实例存在。所以我的问题是:

  1. 第一个实现如何强制执行单例模式?我认为它与 static 关键字有关,但我希望有人可以深入向我解释引擎盖下发生的事情。
  2. 在这两种实现风格之间,一种比另一种更可取吗?优缺点都有什么?

谢谢你的帮助,

4

2 回答 2

58

这是一个单例,因为static本地函数的存储持续时间意味着程序中只存在该本地函数的一个实例。

在幕后,这可以非常粗略地认为等同于以下 C++98(甚至可能被编译器模糊地实现为这样):

static bool __guard = false;
static char __storage[sizeof(Singleton)]; // also align it

Singleton& Instance() {
  if (!__guard ) {
    __guard = true;
    new (__storage) Singleton();
  }
  return *reinterpret_cast<Singleton*>(__storage);
}

// called automatically when the process exits
void __destruct() {
  if (__guard)
    reinterpret_cast<Singleton*>(__storage)->~Singleton();
}

线程安全位使它变得有点复杂,但本质上是一样的。

查看 C++11 的实际实现,每个静态变量(如上面的布尔值)都有一个保护变量,它也用于屏障和线程。查看 Clang 的 AMD64 输出:

Singleton& instance() {
   static Singleton instance;
   return instance;
}

来自 Ubuntu 的 Clang 3.0的 AMD64 程序集instance在 AMD64 at -O1(由http://gcc.godbolt.org/提供)是:

instance():                           # @instance()
  pushq %rbp
  movq  %rsp, %rbp
  movb  guard variable for instance()::instance(%rip), %al
  testb %al, %al
  jne   .LBB0_3
  movl  guard variable for instance()::instance, %edi
  callq __cxa_guard_acquire
  testl %eax, %eax
  je    .LBB0_3
  movl  instance()::instance, %edi
  callq Singleton::Singleton()
  movl  guard variable for instance()::instance, %edi
  callq __cxa_guard_release
.LBB0_3:
  movl  instance()::instance, %eax
  popq  %rbp
  ret

你可以看到它引用了一个全局守卫来查看是否需要初始化、使用__cxa_guard_acquire、再次测试初始化​​等等。除了使用 AMD64 程序集和Itanium ABI中指定的符号/布局之外,几乎在所有方面都与您从 Wikipedia 发布的版本完全一样。

请注意,如果您运行该测试,您应该提供Singleton一个重要的构造函数,因此它不是 POD,否则优化器将意识到做所有这些保护/锁定工作是没有意义的。

于 2013-07-18T03:01:23.873 回答
24
// Singleton.hpp
class Singleton {
public:
    static Singleton& Instance() {
        static Singleton S;
        return S;
    }

private:
    Singleton();
    ~Singleton();
};

这种实现被称为 Meyers 的 Singleton。斯科特迈耶斯 说:

“这种方法建立在 C++ 保证在调用该函数期间首次遇到对象定义时初始化本地静态对象。” ...“作为奖励,如果您从不调用模拟非本地静态对象的函数,则永远不会产生构造和破坏对象的成本。”

当您 Singleton& s=Singleton::Instance() 第一次调用创建对象时,每次下一次调用都会Singleton::Instance()返回相同的对象。主要问题:


另一种实现称为可信赖的泄漏单例。

class Singleton {
public:
    static Singleton& Instance() {
        if (I == nullptr) { I = new Singleton(); }
        return *I;
    }

private:
    Singleton();
    ~Singleton();

    static Singleton* I;
};

// Singleton.cpp
Singleton* Singleton::I = 0;

两个问题:

  • 泄漏,除非您实现Release并确保调用它(一次)
  • 不是线程安全的
于 2013-07-18T00:20:51.613 回答