【问题标题】:C++ Singleton design patternC++ 单例设计模式
【发布时间】:2010-11-03 17:28:25
【问题描述】:

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

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

从这个声明中,我可以推断出实例字段是在堆上启动的。这意味着有内存分配。对我来说完全不清楚的是何时释放内存?还是有错误和内存泄漏?执行起来好像有问题。

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

【问题讨论】:

  • 您将在本文中找到关于如何实现单例以及 C++ 中的线程安全的精彩讨论。 aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
  • @sbi - 只有西斯在绝对交易。如果没有 Singletons,绝大多数问题都可以解决吗?绝对地。单身人士会导致自己的问题吗?是的。但是,我不能老实说它们不好,因为设计就是要考虑权衡和理解方法的细微差别。
  • @derekerdmann:我并不是说你永远不需要全局变量(当你需要一个全局变量时,单例有时会更好)。我说的是应该尽可能少地使用它们。将 Singleton 美化为一种有价值的设计模式给人的印象是使用它很好,而不是它是一种 hack,使代码难以理解、难以维护和难以测试。这就是我发表评论的原因。到目前为止,您所说的没有一个与此相矛盾。
  • @sbi:你说的是“不要使用它们”。不是你后来改成的更合理的“它们应该尽可能少地使用”——你肯定看到了区别。

标签: c++ design-patterns singleton


【解决方案1】:

在 2008 年,我提供了单例设计模式的 C++98 实现,它是惰性求值、保证破坏、非技术线程安全的:
Can any one provide me a sample of Singleton in c++?

这里是单例设计模式的更新 C++11 实现,它是惰性求值、正确销毁和 thread-safe

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
};

请参阅这篇关于何时使用单例的文章:(不经常)
Singleton: How should it be used

关于初始化顺序和如何应对请看这两篇文章:
Static variables initialisation order
Finding C++ static initialization order problems

请参阅这篇描述生命周期的文章:
What is the lifetime of a static variable in a C++ function?

请参阅这篇讨论线程对单例的一些影响的文章:
Singleton instance declared as static variable of GetInstance method, is it thread-safe?

请参阅这篇解释为什么双重检查锁定在 C++ 上不起作用的文章:
What are all the common undefined behaviours that a C++ programmer should know about?
Dr Dobbs: C++ and The Perils of Double-Checked Locking: Part I

【讨论】:

  • 好答案。但应该注意这不是线程安全的stackoverflow.com/questions/1661529/…
  • @zourtney:很多人没有意识到你刚刚做了什么 :)
  • @MaximYegorushkin:当它被销毁时,定义非常明确(没有歧义)。见:stackoverflow.com/questions/246564/…
  • What irks me most though is the run-time check of the hidden boolean in getInstance() 这是对实现技术的假设。不需要假设它还活着。请参阅stackoverflow.com/a/335746/14065 您可以强制一种情况使其始终处于活动状态(开销低于Schwarz counter)。全局变量在初始化顺序(跨编译单元)方面存在更多问题,因为您不强制执行顺序。该模型的优点是 1) 延迟初始化。 2)执行命令的能力(施瓦茨有帮助,但更丑陋)。是的get_instance() 更丑。
  • @kol:不。这不是通常的。仅仅因为初学者不假思索地复制和粘贴代码并不能使它成为通常的代码。您应该始终查看用例并确保赋值运算符执行预期的操作。复制和粘贴代码会导致错误。
【解决方案2】:

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

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

【讨论】:

  • 这不是内存泄漏,而是一个简单的全局变量声明。
  • 直截了当...“内存泄漏”问题与单例完全无关。如果您有解构顺序很重要的有状态资源,那么单例可能很危险;但是在程序终止时,操作系统会干净地重新获得所有内存......在 99.9% 的情况下,这完全是学术点。如果您想来回争论什么是“内存泄漏”和不是“内存泄漏”的语法,那很好,但要意识到这会分散实际设计决策的注意力。
  • @jkerian:C++ 上下文中的内存泄漏和破坏实际上与内存泄漏无关。真的是关于资源控制。如果您泄漏内存,则不会调用析构函数,因此与对象关联的任何资源都不会正确释放。内存只是我们在教授编程时使用的简单示例,但还有更复杂的资源。
  • @Martin 我完全同意你的看法。即使唯一的资源是内存,如果您必须遍历泄漏列表,过滤掉“无关紧要”的泄漏,您仍然会在试图在程序中找到真正的泄漏时遇到麻烦。最好将这些都清理干净,这样任何报告泄漏的工具都只会报告存在问题的事情。
  • 隐约值得考虑的是存在 C++ 实现(甚至可能是托管的),其中“操作系统”不会在您的程序退出时恢复所有资源,但确实有一些“再次运行程序”的概念,它为您提供了一组全新的全局变量和静态局部变量。在这样的系统上,一个未释放的单例在任何合理的定义下都是真正的泄漏:如果你的程序运行了足够多的时间,它就会关闭系统。您是否关心此类系统的可移植性是另一回事——只要您不编写库,您几乎肯定不会。
【解决方案3】:

您可以避免内存分配。有很多变种,在多线程环境下都有问题。

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

class Singleton
{
private:
   Singleton();

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

它没有动态内存分配。

【讨论】:

  • 在某些情况下,这种延迟初始化并不是理想的模式。一个例子是,如果单例的构造函数从堆中分配内存,并且您希望该分配是可预测的,例如在嵌入式系统或其他严格控制的环境中。当单例模式是最好的模式时,我更喜欢将实例创建为类的静态成员。
  • 适用于许多大型程序,尤其是那些具有动态库的程序。由于库卸载时的破坏顺序问题,任何非原始的全局或静态对象都可能在许多平台上导致程序退出时出现段错误/崩溃。这是许多编码约定(包括 Google 的)禁止使用重要的静态和全局对象的原因之一。
  • 这样的实现中的静态实例似乎有内部链接,并且在不同的翻译单元中会有唯一且独立的副本,这会导致混乱和错误的行为。但是我看到了很多这样的实现,我错过了什么吗?
  • 是什么阻止了用户将其分配给多个对象,而幕后的编译器使用自己的复制构造函数?
  • @Tony 没有什么能阻止复制,你是对的。复制构造函数应该被删除。
【解决方案4】:

@Loki Astari's answer 非常棒。

但是,有时有多个静态对象,您需要能够保证 singleton 不会被销毁,直到所有使用 singleton 的静态对象都没有不再需要它。

在这种情况下,std::shared_ptr 可用于为所有用户保持 singleton 活动,即使在程序结束时调用静态析构函数:

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() {}
};

【讨论】:

  • 你能用= delete解释这两行吗,作为一个C#程序员,这个语法对我来说有点奇怪。或者你能提供一个链接,让我可以阅读这个确切的语法吗?
  • @MohammedNoureldin 默认情况下,C++ 会自动生成复制对象的函数。如果您想防止您的对象被复制,您可以“删除”这些功能。所以= delete 告诉编译器不要生成它们。
  • 这是否实现了未完成的常见问题isocpp.org/wiki/faq/ctors#nifty-counter-idiom中提到的Nifty Counter模式?
  • @RexYuan 是的,我相信。它将确保您的 singleton 对象 在需要它的最后一个组件首先被销毁之前不会被销毁。但是您需要确保单例本身在其销毁期间不需要任何全局静态对象,并且只要您没有做任何愚蠢的事情,例如在 @987654328 之外保留原始指针或对其目标对象的原始引用@.
【解决方案5】:

另一种非分配方式:根据需要创建一个单例,例如 C 类:

singleton<C>()

使用

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

无论是这个还是 Cătălin 的答案在当前 C++ 中都不是自动线程安全的,但会在 C++0x 中。

【讨论】:

  • 目前在 gcc 下它是线程安全的(并且已经有一段时间了)。
  • 这种设计的问题在于,如果跨多个库使用。每个库都有该库使用的单例的自己的副本。所以它不再是单例了。
【解决方案6】:

我没有在答案中找到 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;
    }
};

要使用继承你的类,比如:class Test : public Singleton&lt;Test&gt;

【讨论】:

  • 在我将默认构造函数设置为受保护和 '= default;' 之前,无法使其与 C++17 一起使用。
【解决方案7】:

这是一个简单的实现。

#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 是单例对象的内存位置,在程序运行期间相同,但(通常!)每次程序运行时都不同。

注意这不是线程安全的。你必须确保线程安全。

【讨论】:

    【解决方案8】:

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

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

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

    【讨论】:

    • 如果您指的是当前接受的答案,那么您的第一个陈述是错误的。在销毁所有静态存储持续时间对象之前,不会调用析构函数。
    【解决方案9】:

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

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

    第一种方式:

    按照您在示例中的方式实现它。至于销毁,“单例通常会在程序运行期间持续存在;大多数操作系统会在程序终止时恢复内存和大多数其他资源,因此有理由不担心这一点。”

    但是,最好在程序终止时进行清理。因此,您可以使用辅助静态 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;
    }
    

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

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

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

    【讨论】:

      【解决方案10】:

      有没有人提到std::call_oncestd::once_flag? 大多数其他方法 - 包括双重检查锁定 - 都被破坏了。

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

      【讨论】:

        【解决方案11】:

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

        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);
        

        【讨论】:

        【解决方案12】:

        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;
         }
        

        【讨论】:

        • 如果您的 C++ 编译器符合标准,则简单的局部静态函数变量是线程安全的。不需要所有的互斥魔法。这确实意味着静态初始化程序可能会导致死锁,因此需要小心,但您在此处建议的代码也是如此。
        【解决方案13】:

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

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

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

        ...然后依靠超出范围的程序进行清理。

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

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

        【讨论】:

        • 你可以推断,因为你可以看到实例变量是一个指向类实例的指针。
        • 不需要动态分配单例。事实上,这是一个坏主意,因为没有办法使用上述设计自动取消分配。让它超出范围是不调用析构函数,只是懒惰。
        • 您可以使用 atexit 函数自动解除分配。这就是我们所做的(不是说这是个好主意)
        【解决方案14】:

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

        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); 
        

        refs 不需要将指针存储回它们各自的Store,因为该信息是在编译时提供的。您也不必担心Store 的生命周期,因为编译器要求它是全局的。如果确实只有一个 Store 实例,那么这种方法没有开销;对于不止一个实例,编译器在代码生成方面是否聪明。如有必要,ItemRef 类甚至可以成为Storefriend(你可以有模板化的朋友!)。

        如果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>. */ };
        

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

        【讨论】:

          【解决方案15】:

          这是一个使用CRTPmockable singleton。它依赖a little helper 在任何时候(最多)强制执行单个对象。要强制执行程序执行单个对象,请移除重置(我们发现它对测试很有用)。

          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();
          

          【讨论】:

            【解决方案16】:

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

            【讨论】:

              【解决方案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;
              

              【讨论】:

                【解决方案18】:

                你的代码是正确的,除了你没有在类之外声明实例指针。静态变量的内部类声明在 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
                

                你要知道Singleton实例不需要我们手动删除。我们在整个程序中都需要它的一个对象,所以在程序执行结束时,它会被自动释放。

                【讨论】:

                  【解决方案19】:

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

                  总结:

                  1. 使用静态初始化列表适时实例化单例:进入 main 之后,启用多线程之前
                  2. 添加一些小的改进以使其对单元测试友好。

                  【讨论】:

                    【解决方案20】:

                    我想在这里展示另一个 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 类允许您更具体地定义单例(有时您希望移动单个实例)。

                    【讨论】:

                      【解决方案21】:

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

                      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();
                          }
                      

                      【讨论】:

                      • 这有两个问题。 (1) getInstance() 不是线程安全的:如果多个线程同时调用 getInstance() 则可以构造多个 Singleton 实例,这意味着您有内存泄漏。 (2) 如果实例已经存在,getInstance() 没有返回值,所以你有未定义的行为。
                      【解决方案22】:

                      上面链接的论文描述了双重检查锁定的缺点是编译器可能会在调用对象的构造函数之前为对象分配内存并设置指向分配内存地址的指针。在 c++ 中很容易使用分配器手动分配内存,然后使用构造调用来初始化内存。使用这种方法,双重检查锁定工作得很好。

                      【讨论】:

                      • 很遗憾没有。一些最优秀的 C++ 开发人员已经深入讨论了这一点。双重检查锁定在 C++03 中被破坏。
                      【解决方案23】:
                      #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);
                      

                      【讨论】:

                        【解决方案24】:

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

                        #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);
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2017-10-12
                          • 2011-06-25
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多