如何实现单例设计模式?

How do you implement the Singleton design pattern?

提问人:Artem Barger 提问时间:6/18/2009 最后编辑:Jan SchultkeArtem Barger 更新时间:9/17/2023 访问量:954580

问:

最近,我遇到了 C++ 的单例设计模式的实现/实现。它看起来像这样(我从现实生活中的例子中采用了它):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

从这个声明中,我可以推断出实例字段是在堆上启动的。这意味着存在内存分配。我完全不清楚的是,内存究竟什么时候会被解除分配?还是存在错误和内存泄漏?似乎实现有问题。

我的主要问题是,我如何以正确的方式实现它?

C++ 设计模式 单例

评论

19赞 Martin York 6/18/2009
Смотритетакже: stackoverflow.com/questions/211237/... 和 stackoverflow.com/questions/270947/... 和 stackoverflow.com/questions/246564/... 和 stackoverflow.com/questions/449436/... 和 stackoverflow.com/questions/335369/ ...
13赞 10/30/2009
在本文中,您将找到有关如何实现单例以及 C++ 中的线程安全的精彩讨论。aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
116赞 derekerdmann 7/29/2011
@sbi - 只有西斯才能处理绝对问题。绝大多数问题可以在没有单例的情况下解决吗?绝对。单身人士会造成自己的问题吗?是的。然而,我不能诚实地说它们是的,因为设计就是要考虑权衡和理解你的方法的细微差别。
13赞 sbi 7/29/2011
@derekerdmann:我并不是说你永远不需要全局变量(当你需要全局变量时,Singleton 有时更好)。我说的是,它们应该尽可能少地使用。将 Singleton 美化为一种有价值的设计模式,给人的印象是使用它是件好事,而不是说它是一种黑客,使代码难以理解、难以维护和难以测试。这就是我发表评论的原因。到目前为止,你说的都没有与此相矛盾。
18赞 jwd 10/18/2011
@sbi:你说的是“不要使用它们”。不是你后来改成的更合理的“它们应该尽可能少地使用”——你肯定看到了区别。

答:

55赞 Reed Copsey 6/18/2009 #1

作为一个单身人士,你通常不希望它被破坏。

当程序终止时,它将被拆除并释放,这是单例的正常、期望的行为。如果你希望能够显式清理它,那么向类中添加一个静态方法相当容易,它允许你将其恢复到干净的状态,并在下次使用时重新分配它,但这超出了“经典”单例的范围。

评论

9赞 ilya n. 6/18/2009
它不再是内存泄漏,而是全局变量的简单声明。
18赞 jkerian 6/18/2009
为了弄清楚一些事情......与单例相关的“内存泄漏”问题完全无关紧要。如果您有有状态的资源,其中解构顺序很重要,则单例可能很危险;但是所有内存都会在程序终止时作系统干净地恢复......在99.9%的情况下,这一完全学术的观点无效。如果你想来回争论什么是“内存泄漏”,什么不是“内存泄漏”,那很好,但要意识到这是对实际设计决策的分心。
12赞 Martin York 6/18/2009
@jkerian:C++上下文中的内存泄漏和破坏实际上与内存泄漏无关。实际上,这与资源控制有关。如果泄漏内存,则不会调用 destroctor,因此无法正确释放与该对象关联的任何资源。内存只是我们在教授编程时使用的简单示例,但那里有更复杂的资源。
9赞 Dolphin 6/18/2009
@Martin我完全同意你的看法。即使唯一的资源是内存,如果您必须浏览泄漏列表,过滤掉“无关紧要”的泄漏,您仍然会遇到麻烦,试图在程序中查找真正的泄漏。最好把这些都清理干净,这样任何报告泄漏的工具都只报告有问题的事情。
8赞 Steve Jessop 1/16/2014
隐约值得考虑的是,存在 C++ 实现(甚至可能是托管的实现),其中“OS”在程序退出时不会恢复所有资源,但确实有一些“再次运行程序”的概念,这为您提供了一组新的全局变量和静态局部变量。在这样的系统上,未释放的单例在任何合理的定义下都是真正的泄漏:如果你的程序运行了足够多的次数,它就会使系统瘫痪。你是否关心这些系统的可移植性是另一回事——只要你不写一个库,你几乎肯定不会。
65赞 Cătălin Pitiș 6/18/2009 #2

您可以避免内存分配。有许多变体,在多线程环境中都存在问题。

我更喜欢这种实现(实际上,我更喜欢这种说法并不正确,因为我尽可能避免单例):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

它没有动态内存分配。

评论

4赞 dma 6/18/2009
在某些情况下,这种延迟初始化不是理想的模式。例如,如果单例的构造函数从堆中分配内存,并且您希望该分配是可预测的,例如在嵌入式系统或其他严格控制的环境中。当 Singleton 模式是要使用的最佳模式时,我更喜欢将实例创建为类的静态成员。
3赞 obecalp 6/18/2009
适用于许多大型程序,尤其是那些具有动态库的程序。由于库卸载时的销毁顺序问题,任何非原语的全局或静态对象都可能导致在许多平台上退出程序时出现段错误/崩溃。这是许多编码约定(包括 Google 的)禁止使用非平凡的静态和全局对象的原因之一。
0赞 FaceBro 2/25/2017
这种实现中的静态实例似乎具有内部链接,并且在不同的翻译单元中会有唯一且独立的副本,这会导致混淆和错误的行为。但是我看到了很多这样的实现,我错过了什么吗?
0赞 Tony Tannous 7/16/2019
是什么阻止用户将其分配给多个对象,而幕后编译器使用自己的复制构造函数?
2赞 ebrahim 5/24/2021
@FaceBro 这里有两个关键字的实例,但从链接的角度来看,它们都没有问题。第一次出现是关于静态成员函数的,这与链接无关。的第二次出现是关于 的存储持续时间。该对象将在程序运行期间存在于内存中,并且您不能在 TU 之外通过名称访问它并不重要,因为您是通过具有外部链接的成员函数访问它。staticstaticstaticINSTANCEinstance
3赞 T.E.D. 6/18/2009 #3

它确实可能是从堆中分配的,但如果没有来源,就无法知道。

典型的实现(取自我在 emacs 中的一些代码)是:

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

...并依靠程序超出范围进行事后清理。

如果您在必须手动完成清理的平台上工作,我可能会添加手动清理例程。

这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程可以在任何一个线程有机会分配新实例之前通过“if”(因此两者都会)。如果您无论如何都依靠程序终止来清理,这仍然没什么大不了的。

评论

0赞 Artem Barger 6/18/2009
你可以推断,因为你可以看到实例变量是指向类实例的指针。
3赞 Martin York 6/18/2009
无需动态分配单例。事实上,这是一个坏主意,因为没有办法使用上述设计自动取消分配。让它超出范围是不调用析构函数,只是懒惰。
0赞 Joe 6/18/2009
您可以使用 atexit 函数自动解除分配。这就是我们所做的(不是说这是一个好主意)
16赞 James Hopkin 6/18/2009 #4

另一个非分配的替代方案:创建一个单例,比如类,根据需要:C

singleton<C>()

template <class X>
X& singleton()
{
    static X x;
    return x;
}

在当前的 C++ 中,这和 Cătălin 的答案都不是自动线程安全的,而是在 C++0x 中。

评论

0赞 Martin York 6/18/2009
目前在 gcc 下,它是线程安全的(并且已经有一段时间了)。
16赞 Martin York 6/18/2009
这种设计的问题在于,如果跨多个库使用。每个库都有自己的该库使用的单例副本。所以它不再是一个单例。
1388赞 Martin York 6/18/2009 #5

在 2008 年,我提供了一个 C++ 98 的 Singleton 设计模式实现,它是延迟评估的、保证销毁的、非技术线程安全的:
任何人都可以为我提供 c++ 中的 Singleton 示例吗?

下面是 Singleton 设计模式的更新的 C++11 实现,该模式具有延迟计算、正确销毁和线程安全

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are inaccessible(especially from outside), otherwise, you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};

请参阅这篇关于何时使用单例的文章:(不经常)
单例:应该如何使用它

请参阅这两篇关于初始化顺序以及如何应对的文章:
静态变量初始化顺序 查找 C++ 静态初始化顺序问题

请参阅这篇描述生存期的文章:
C++ 函数中静态变量的生存期是多少?

请参阅这篇文章,其中讨论了对单例的一些线程影响:
声明为 GetInstance 方法的静态变量的单例实例,它是线程安全的吗?

请参阅这篇文章,它解释了为什么双重检查锁定在 C++ 上不起作用:
C++ 程序员应该了解的所有常见未定义行为是什么?
Dobbs博士:C++和双重检查锁定的危险:第一部分

评论

33赞 Varuna 12/25/2009
好答案。但应该注意,这不是线程安全的 stackoverflow.com/questions/1661529/......
3赞 Martin York 12/25/2009
上面已经提到过:stackoverflow.com/questions/449436/...
3赞 Martin York 7/30/2012
§6.7 [stmt.dcl] 第4页If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
6赞 Martin York 5/21/2013
@MaximYegorushkin:当它被销毁时,定义非常明确(没有歧义)。请参阅:stackoverflow.com/questions/246564/...
7赞 Martin York 5/21/2013
What irks me most though is the run-time check of the hidden boolean in getInstance()这是对实现技术的假设。不需要假设它是活着的。请参阅 stackoverflow.com/a/335746/14065 您可以强制一种情况,使其始终处于活动状态(开销小于 )。全局变量在初始化顺序(跨编译单元)方面存在更多问题,因为您不强制顺序。这种模型的优点是 1) 延迟初始化。2)执行命令的能力(施瓦茨有帮助,但更丑陋)。是的,要丑得多。Schwarz counterget_instance()
7赞 SadSido 6/18/2009 #6

已接受答案中的解决方案有一个明显的缺点 - 单例的析构函数在控件离开函数后被调用。当在内部分配一些依赖对象时,确实可能会出现问题。main()main

当我尝试在Qt应用程序中引入Singleton时,我遇到了这个问题。我决定,我所有的设置对话框都必须是单例对话框,并采用了上面的模式。不幸的是,Qt的主类被分配在函数的堆栈上,Qt禁止在没有应用程序对象可用时创建/销毁对话框。QApplicationmain

这就是为什么我更喜欢堆分配的单例。我为所有单例提供了一个显式和方法,并在内部调用它们。因此,我可以完全控制单例创建/销毁的顺序,并且我保证无论是否有人调用,都会创建单例。init()term()maingetInstance()

评论

4赞 Martin York 10/13/2012
如果你指的是当前接受的答案,你的第一个陈述是错误的。在销毁所有静态存储持续时间对象之前,不会调用析构函数。
1赞 baris.aydinoz 4/1/2010 #7

这是关于对象生存期管理的。假设您的软件中不止有单例。它们依赖于 Logger 单例。在应用程序销毁期间,假设另一个单例对象使用 Logger 来记录其销毁步骤。您必须保证最后清理 Logger。因此,也请查看这篇论文: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf

-1赞 sdfsdaf 10/18/2011 #8

上面链接的论文描述了双重检查锁定的缺点,即编译器可能会在调用对象的构造函数之前为对象分配内存,并设置指向已分配内存地址的指针。但是,在 c++ 中,使用分配器手动分配内存,然后使用构造调用来初始化内存非常容易。使用此应用程序,双重检查锁定工作正常。

评论

2赞 Martin York 10/13/2012
很遗憾没有。一些最好的 C++ 开发人员已经对此进行了深入的讨论。在 C++03 中,双重检查锁定被破坏。
5赞 riderchap 1/12/2013 #9

如果要在堆中分配对象,为什么不使用唯一指针。内存也将被释放,因为我们使用的是唯一的指针。

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);

评论

3赞 Andrew 9/24/2016
在 c++11 中不推荐使用。建议改用unique_ptr。cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
3赞 Galik 4/12/2017
这不是线程安全的。最好在不进行测试的情况下立即创建本地并初始化它。m_sstaticgetInstance()
0赞 ChrisZZ 11/21/2021
与 相比会比与 .m_s.get()nullptr0
-2赞 Gank 1/19/2014 #10
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

例:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);
8赞 Tunvir Rahman Tusher 5/8/2014 #11

这是一个简单的实现。

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}

只创建一个对象,并且每次后记都会返回此对象引用。

SingletonClass instance created!
00915CB8
00915CB8

这里 00915CB8 是单例对象的内存位置,在程序的持续时间内相同,但(通常)每次运行程序时都不同。

注意这不是一个线程安全的线程。您必须确保线程安全。

1赞 dan-man 11/6/2015 #12

除了此处的其他讨论之外,可能值得注意的是,您可以具有全局性,而不限制使用一个实例。例如,考虑引用计数的情况......

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

现在,在函数(例如)中的某个地方,您可以执行以下操作:main

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

引用不需要存储指向各自的指针,因为该信息是在编译时提供的。您也不必担心 的生存期,因为编译器要求它是全局的。如果确实只有一个实例,则此方法没有开销;对于多个实例,编译器在代码生成方面要聪明。如有必要,甚至可以将班级制作成一个(您可以有模板化的朋友!StoreStoreStoreItemReffriendStore

如果它本身是一个模板化类,那么事情会变得更混乱,但仍然可以使用这种方法,也许可以通过实现具有以下签名的帮助程序类:Store

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

用户现在可以为每个全局实例创建一个类型(和全局实例),并始终通过其包装实例访问存储(从而忘记了使用所需的模板参数的血腥细节)。StoreWrapperStoreStore

43赞 Galik 10/31/2016 #13

@Loki Astari 的回答非常好。

但是,有时对于多个静态对象,您需要能够保证在使用该例的所有静态对象不再需要它之前不会销毁该单例

在这种情况下,可用于使所有用户的单例保持活动状态,即使在程序结束时调用静态析构函数也是如此:std::shared_ptr

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};

评论

0赞 Mohammed Noureldin 11/1/2020
作为 C# 程序员,您能否解释一下这两行,这种语法对我来说看起来有点奇怪。或者你能提供一个链接,让我可以读到这个确切的语法吗?= delete
2赞 Galik 11/1/2020
@MohammedNoureldin 默认情况下,将自动生成函数来复制对象。如果要防止对象被复制,可以“删除”这些函数。所以告诉编译器不要生成它们。C++= delete
0赞 RexYuan 4/14/2021
这是否实现了未完成的常见问题解答 isocpp.org/wiki/faq/ctors#nifty-counter-idiom 中提到的 Nifty Counter 模式?
0赞 Galik 4/14/2021
@RexYuan 是的,我相信是的。它将确保你的单例对象不会被销毁,直到需要它的最后一个组件首先被销毁。但是你需要确保单例本身在销毁过程中不需要任何全局静态对象,只要你没有做任何愚蠢的事情,比如将原始指针或对其目标对象的原始引用保留在 .std::shared_ptr
2赞 Joseph Riesen 10/29/2022
我已经开始使用这种方法来维护一些过于复杂(单例繁重)的遗留软件。当我研究我们在终止期间发生的神秘崩溃时,我发现有几个单例持有指向其他单例的指针,并在销毁过程中对它们调用方法,导致整个静态反初始化顺序惨败。切换到此方法消除了运行时无序销毁单例时发生的释放后使用错误。真的值得更多的投票,谢谢!
13赞 Yuriy 2/12/2018 #14

我在答案中没有找到 CRTP 实现,所以这里是:

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

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

要使用 just inherit your class from this,例如:class Test : public Singleton<Test>

评论

4赞 wfranczyk 5/25/2019
在我使默认构造函数受到保护并且“= default;”之前,无法将其与 C++17 一起使用。
8赞 Red.Wave 4/10/2018 #15

有没有人提到过? 大多数其他方法(包括双重检查锁定)都已损坏。std::call_oncestd::once_flag

单例模式实现中的一个主要问题是安全初始化。唯一安全的方法是使用同步屏障来保护初始化序列。但这些障碍本身需要安全地启动。 是获得保证安全初始化的机制。std::once_flag

-1赞 Ali Khazaee 9/11/2018 #16

简单的单例类,这一定是你的头类文件

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

像这样访问您的单例:

sSingletonClass->Relocate(1, 2, 5);
0赞 Kevin Marshall 11/20/2019 #17

我的实现类似于 Galik 的实现。不同之处在于,我的实现允许共享指针清理分配的内存,而不是保留内存,直到退出应用程序并清理静态指针。

#pragma once

#include <memory>

template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }

    return singleton;
  }
};

template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;
10赞 Tony Bai 11/20/2019 #18

我们最近在EECS课上讨论了这个话题。如果您想详细查看讲义,请访问 http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf。这些笔记(以及我在这个答案中给出的引文)是由我的教授大卫·基拉斯(David Kieras)创作的。

我知道有两种方法可以正确创建 Singleton 类。

第一种方式:

实现它的方式类似于示例中的实现方式。至于破坏,“单例通常会忍受程序运行的长度;当程序终止时,大多数操作系统都会恢复内存和大多数其他资源,因此有理由不担心这一点。

但是,最好在程序终止时进行清理。因此,您可以使用辅助静态 SingletonDestructor 类执行此操作,并在 Singleton 中将其声明为朋友。

class Singleton {
public:
  static Singleton* get_instance();
  
  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;

  friend class Singleton_destroyer;

private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion

  static Singleton* ptr;
};

// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};

// somewhere in code (Singleton.cpp is probably the best place) 
// create a global static Singleton_destroyer object
Singleton_destoyer the_destroyer;

Singleton_destroyer将在程序启动时创建,“当程序终止时,所有全局/静态对象都会被运行时库关闭代码(由链接器插入)销毁,因此the_destroyer将被销毁;它的析构函数将删除 Singleton,运行其析构函数。

第二种方式

这被称为Meyers Singleton,由C++向导Scott Meyers创建。只需以不同的方式定义 get_instance()。现在,您还可以删除指针成员变量。

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

这很简洁,因为返回的值是通过引用的,您可以使用语法而不是访问成员变量。.->

“编译器会自动构建代码,通过 声明,而不是此后,然后在程序中删除静态对象 终止。

还要注意的是,使用 Meyers Singleton,您“如果对象在 终止 - Singleton 何时相对于其他对象消失?但对于简单的应用程序,这很好用。

评论

0赞 Martin York 9/23/2023
相对简单(且直观)的静态初始化/销毁顺序问题的解决方案。stackoverflow.com/a/335746/14065
0赞 Ali Sajjad 1/10/2020 #19

您的代码是正确的,只是您没有在类外部声明实例指针。静态变量的内部类声明在 C++ 中不被视为声明,但在其他语言(如 C#Java 等)中是允许的。

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

要知道,我们不需要手动删除单例实例。在整个程序中,我们需要它的单个对象,因此在程序执行结束时,它将自动解除分配。

1赞 uuu777 6/21/2020 #20

以下是我对如何做适当的单例(和其他重要的静态对象)的看法: https://github.com/alex4747-pub/proper_singleton

总结:

  1. 使用静态初始化列表在正确的时间实例化单例:输入 main 之后和启用多线程之前
  2. 添加一些小的改进,使其对单元测试友好。
4赞 Milind Deore 7/12/2020 #21

C++11 线程安全实现:

 #include <iostream>
 #include <thread>


 class Singleton
 {
     private:
         static Singleton * _instance;
         static std::mutex mutex_;

     protected:
         Singleton(const std::string value): value_(value)
         {
         }
         ~Singleton() {}
         std::string value_;

     public:
         /**
          * Singletons should not be cloneable.
          */
         Singleton(Singleton &other) = delete;
         /**
          * Singletons should not be assignable.
          */
         void operator=(const Singleton &) = delete;

         //static Singleton *GetInstance(const std::string& value);
         static Singleton *GetInstance(const std::string& value)
         {
             if (_instance == nullptr)
             {
                 std::lock_guard<std::mutex> lock(mutex_);
                 if (_instance == nullptr)
                 {
                     _instance = new Singleton(value);
                 }
             }
             return _instance;
         }

         std::string value() const{
             return value_;
         }
 };

 /**
  * Static methods should be defined outside the class.
  */
 Singleton* Singleton::_instance = nullptr;
 std::mutex Singleton::mutex_;


 void ThreadFoo(){
     std::this_thread::sleep_for(std::chrono::milliseconds(10));
     Singleton* singleton = Singleton::GetInstance("FOO");
     std::cout << singleton->value() << "\n";
 }

 void ThreadBar(){
     std::this_thread::sleep_for(std::chrono::milliseconds(1000));
     Singleton* singleton = Singleton::GetInstance("BAR");
     std::cout << singleton->value() << "\n";
 }

 int main()
 {
     std::cout <<"If you see the same value, then singleton was reused (yay!\n" <<
                 "If you see different values, then 2 singletons were created (booo!!)\n\n" <<
                 "RESULT:\n";
     std::thread t1(ThreadFoo);
     std::thread t2(ThreadBar);
     t1.join();
     t2.join();
     std::cout << "Complete!" << std::endl;

     return 0;
 }

评论

4赞 ozzee 5/19/2021
如果 C++ 编译器符合标准,则简单的局部静态函数变量是线程安全的。不需要所有的互斥魔术。这确实意味着静态初始值设定项可能会导致死锁,因此需要小心,但您建议的代码也是如此。
0赞 uuu777 6/15/2023
局部静态变量的问题在于它不可单元测试。
2赞 ricab 8/5/2020 #22

这是一个使用 CRTP 的可模拟单例。它依赖于一个小帮助程序在任何时候(最多)强制执行单个对象。要强制执行单个对象,请删除重置(我们发现这对测试很有用)。

A 可以这样实现:ConcreteSinleton

class ConcreteSingleton : public Singleton<ConcreteSingleton>
{
public:
  ConcreteSingleton(const Singleton<ConcreteSingleton>::PrivatePass&)
      : Singleton<StandardPaths>::Singleton{pass}
  {}
  
  // ... concrete interface
  int f() const {return 42;}

};

然后与

ConcreteSingleton::instance().f();
1赞 Andrushenko Alexander 1/28/2021 #23

我想在这里展示另一个 C++ 中的单例示例。使用模板编程是有意义的。此外,从不可复制且不可移动的类派生单例类是有意义的。以下是它在代码中的样子:

#include<iostream>
#include<string>

class DoNotCopy
{
protected:
    DoNotCopy(void) = default;
    DoNotCopy(const DoNotCopy&) = delete;
    DoNotCopy& operator=(const DoNotCopy&) = delete;
};

class DoNotMove
{
protected:
    DoNotMove(void) = default;
    DoNotMove(DoNotMove&&) = delete;
    DoNotMove& operator=(DoNotMove&&) = delete;
};

class DoNotCopyMove : public DoNotCopy,
    public DoNotMove
{
protected:
    DoNotCopyMove(void) = default;
};

template<class T>
class Singleton : public DoNotCopyMove
{
public:
    static T& Instance(void)
    {
        static T instance;
        return instance;
    }

protected:
    Singleton(void) = default;
};

class Logger final: public Singleton<Logger>
{
public:
    void log(const std::string& str) { std::cout << str << std::endl; }
};



int main()
{
    Logger::Instance().log("xx");
}

拆分为 NotCopyable 和 NotMovable clases 允许您更具体地定义单例(有时您希望移动单个实例)。

1赞 Sarath Govind 6/26/2021 #24

它将类的实例化限制为一个对象。当只需要一个对象来协调整个系统的操作时,这很有用

class Singleton {
private:
    int data;
    static Singleton* instance;
    Singleton();
public:
    static Singleton* getInstance();
};
Singleton* Singleton::instance = 0;
Singleton::Singleton()
{
    this->data = 0;
    cout << "constructor called.." << endl;
}

 

Singleton* Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
        return instance;
    }
}
int main() {
    Singleton *s = s->getInstance();
    Singleton *s1 =s1->getInstance();
    }

评论

0赞 jezza 2/3/2022
这有两个问题。(1) getInstance() 不是线程安全的:如果多个线程同时调用 getInstance(),则可能会构造多个单例实例,这意味着您存在内存泄漏。(2) 如果实例已经存在,getInstance() 没有返回值,所以你有未定义的行为。