c++中的单例工作有多精确

How precisely does a singleton work in C++

本文关键字:工作 单例 c++      更新时间:2023-10-16

我理解你为什么要使用单例。然而,我不明白它们是如何工作的。如果这个单例对象之前已经创建过一次,并且getinstance"方法在其他地方被调用,它如何知道在内存中的单例的原始实例是什么?从阅读代码我不能理解它是从哪里来的。以下是来自Microsoft MSDN网站的简单示例。

class Singleton {
public: 
    static Singleton* Instance();
protected: 
    Singleton();
private:
    static Singleton* _instance;
}
// Implementation 
Singleton* Singleton::_instance = 0;
Singleton* Singleton::Instance() {
    if (_instance == 0) {
        _instance = new Singleton;
    }
    return _instance;
}

我很确定它来自这一行

Singleton* Singleton::_instance = 0;

但我不知道这一行到底是做什么的,也不知道它是如何工作的。

提前感谢

真是个可怕的例子。让我们看一下细节:

    static Singleton* Instance();

函数应该返回Singleton&,而不是Singleton*

protected: 
    Singleton();

protected仅对设计为派生的类有意义。从Singleton派生并没有多大意义。往好了说,这是一个奇异的特例。单例构造函数通常是private

Singleton* Singleton::_instance = 0;

从c++ 11开始,nullptr应该用于空指针。

Singleton* Singleton::Instance() {
    if (_instance == 0) {
        _instance = new Singleton;
    }
    return _instance;
}

不是线程安全的。假设有3个线程(A, B和C)同时调用Instance()。这三个节点第一次同时到达if (_instance == 0)。因此,条件对它们都为真,所以它们都进入if块-导致三个实例被创建!

我很确定它来自这一行

Singleton* Singleton::_instance = 0;

但我不知道这一行到底是做什么的,也不知道它是如何工作的。

初始化Singleton类的static _instance成员变量。在此上下文中,static意味着变量独立于任何Singleton实例存在,因此您可以在static成员函数中访问它,如Instance()


从c++ 11开始,实现单例的线程安全方法是使用static局部变量:

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

该技术已经存在了很长时间,但直到c++ 11之后才成为线程安全的,因为只有c++ 11标准正式承认多线程的存在,包括对局部static变量的某些保证。

这种技术的缺点是,如果你有多个单例类,并且一个单例类的析构函数访问另一个单例类,你可能会遇到销毁顺序问题。

有解决这个问题的方法,但我不会在这里深入讨论,因为现在你已经学习了一些关于Singleton的事情- 不要使用模式

现在大多数程序员都经历了惨痛的教训,singleton只是伪装的全局变量,应该尽量少用。它们在你的代码中创建了全局依赖,使得测试和模块化变得更加困难或不可能,很难正确和安全地实现。Singleton是《四人帮》里的头号败类。


顺便说一下,看看微软自己在你引用的页面顶部是怎么说的:

此内容已过时,不再维护。它是提供给仍在使用这些服务的个人是一种礼貌技术。

如果单例已经创建了一次之前和"get实例"方法已被调用在其他地方,它如何知道在内存中的单例的原始实例是什么?

静态数据通常存储在可执行文件的。data部分。

当你的代码使用静态数据时,它将被翻译成从数据段引用一些绝对地址的汇编指令,例如

A1 00 F0 22 01       mov         eax,dword ptr ds:[0122F000h]

你的代码被编译和链接,假设你的程序将被加载到某个预期的基址。但情况并非总是如此,因此你的可执行文件还包含重定位部分,其中包含有关文件中这些绝对地址的偏移量信息。

当你的程序被加载到内存中时,操作系统会注意将这些绝对地址替换为实际地址。

下面是一个不那么令人困惑的例子。让我们只保留Singleton作为设计模式的名称,而不使用它作为类的名称。

Logger* Logger::m_pInstance = NULL; 
/* Use this to get the instance of Logger instead of "new" */
Logger* Logger::Instance()
{
     if (!m_pInstance) {  // Only allow one instance of class to be generated.
         m_pInstance = new Logger;
     }
     return m_pInstance;
}

/* some other method in the logger class */
bool Logger::openLogFile(std::string _logFile)
{
   ...
   ...
   ...
}
基本上,如果你想要这个类的一个实例,你调用instance()方法。它将始终返回完全相同的对象给你。 是的,有些人因为这样或那样的原因不喜欢Singleton…不知道为什么。所有的模式都有自己的位置和用途。 学习尽可能多的设计模式以及何时使用它们,总比不知道它们要好。https://en.wikipedia.org/wiki/Software_design_pattern

单例模式有时非常有用。

这句话的意思是:

Singleton* Singleton::Instance() {
    if (_instance == 0) {
        _instance = new Singleton;
    }
    return _instance;
}

已经创建了这个对象(在这个例子中是Singleton类型),如果是,返回它。如果没有,创建新的"Singleton"对象并返回。

换句话说,无论你调用Instance()方法多少次,它总是返回相同的对象给你。