Meyers 的单例实现实际上是如何实现单例的

Posted

技术标签:

【中文标题】Meyers 的单例实现实际上是如何实现单例的【英文标题】:How is Meyers' implementation of a Singleton actually a Singleton 【发布时间】:2013-07-16 17:26:53 【问题描述】:

我已经阅读了很多关于单例、何时应该使用和不应该使用它们以及如何安全地实现它们的信息。我正在用 C++11 编写,并且遇到了 Meyer 的单例延迟初始化实现,如 this question.

所示

这个实现是:

static Singleton& instance()

     static Singleton s;
     return s;

我了解这对于 SO 上的其他问题来说是线程安全的,但我不明白这实际上是一个单例模式。我已经用其他语言实现了单例,这些总是以Wikipedia 中的这个例子结束:

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;
        

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

    第一个实现如何强制执行单例模式?我认为它与 static 关键字有关,但我希望有人可以深入向我解释幕后发生的事情。 在这两种实现方式之间,哪一种更可取?有什么优缺点?

感谢您的帮助,

【问题讨论】:

C++ 代码,您表示它的方式,与其说是一个单例,不如说它是一个初始化良好的全局对象。没有什么能阻止你拥有另一个(制作另一个功能),这很好。强迫任何人做任何事是没有意义的。如果你只想要一个,就只做一个。 static Singleton s; 表示第一次调用函数时,该变量将被初始化,与非静态变量不同,函数结束后它不会被销毁。因此,当您再次调用它时,它仍然存在,因此第二次(第 3 次,第 4 次,...)时间执行的唯一指令是 return s。 我认为Singleton 的构造函数是私有的,因此实际获取实例的唯一方法是通过instance() 函数。 单身人士:只需说no(另外,您的第一个示例本身并不是单身人士。您遗漏了一些代码。) 我完全不知道为什么这会被关闭为“主要基于意见”。好的,第二个问题可能看起来很像,但很可能(并且实际上是)客观原因更喜欢一个实现而不是另一个,第一个问题虽然缺乏对 static 关键字的理解,但它是一个完全有效的问题, 也。这不仅仅是那些单身的仇恨者尖叫着要血。说真的,我也是其中之一,但这也不会使这个问题无效。 +1 【参考方案1】:

这是一个单例,因为 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;

instance 的 AMD64 程序集来自 Ubuntu 的 Clang 3.0 on 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,否则优化器将意识到做所有这些保护/锁定工作是没有意义的。

【讨论】:

“//线程退出时自动调用”——你的意思是进程退出吗?这种模式与 TLS 无关,是吗? @kkm 我的意思是过程,是的。 感谢您的澄清!我对此有点困惑! :)【参考方案2】:
// Singleton.hpp
class Singleton 
public:
    static Singleton& Instance() 
        static Singleton S;
        return S;
    

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

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

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

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

受破坏令 Fiasco 约束(相当于Initialization Order Fiasco)

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

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 并确保调用它(一次) 不是线程安全的

【讨论】:

对于泄漏单例,为什么不static Singleton&amp; Instance() static Singleton *I = new Singleton(); return *I; 在您的代码Singleton s=Singleton::Instance() 中,您实际上不是通过默认(非私有)复制构造函数创建一个新实例吗? (我会想到Singleton &amp; s=Singleton::Instance() 注意:由于 C++11(但只有 MSVC 14+,以前的版本不兼容),如果您按照@JefferyThomas 的建议使用本地静态,那么这甚至是线程安全的 :)

以上是关于Meyers 的单例实现实际上是如何实现单例的的主要内容,如果未能解决你的问题,请参考以下文章

spring的单例模式

K:单例模式中存在的问题

如何写一个简单的单例模式?

static实现单例的隐患

多线程下的单例模式

Go语言实现单例的正确姿势