C ++ Singleton设计模式

时间:2009-06-17 16:02:23

标签: c++ design-patterns singleton

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

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

从这个声明中我可以推断出实例字段是在堆上启动的。这意味着存在内存分配。对我来说完全不清楚的是,什么时候内存将被解除分配?还是有漏洞和内存泄漏?好像在实施中存在问题。

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

26 个答案:

答案 0 :(得分:971)

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

以下是Singleton设计模式的更新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 unacceptable 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

答案 1 :(得分:45)

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

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

答案 2 :(得分:33)

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

我更喜欢这种实现(实际上,我没有正确地说我更喜欢,因为我尽可能地避免单身人士):

class Singleton
{
private:
   Singleton();

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

它没有动态内存分配。

答案 3 :(得分:11)

@Loki Astari's answer很棒。

但是有时会出现多个静态对象,您需要能够保证在使用单例之后的所有静态对象都不会销毁单例更需要它。

在这种情况下,即使在程序结束时调用静态析构函数,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() {}
};

答案 4 :(得分:9)

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

singleton<C>()
使用

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

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

答案 5 :(得分:6)

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

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

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

答案 6 :(得分:5)

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

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

答案 7 :(得分:4)

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

答案 8 :(得分:3)

这是一个简单的实现。

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

N.B。这不是一个线程安全的。你必须确保线程安全。

答案 9 :(得分:2)

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

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

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

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

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

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

答案 10 :(得分:2)

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

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

答案 11 :(得分:1)

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

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课程甚至可以成为friend Store(你可以有模板化的朋友!)。

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

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

我的实现类似于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;

答案 14 :(得分:0)

我们最近在EECS课堂上讨论了这个主题。如果您想详细阅读讲义,请访问http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf

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

第一种方式:

实现它的方式类似于您在示例中的实现方式。至于破坏,“ Singletons通常在程序运行的过程中会忍受;大多数OS会在程序终止时恢复内存和大多数其他资源,因此有一种不用担心这一点的说法。”

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

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

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

“ Compiler会自动构建代码,该代码会在第一次通过 声明,而不是此后声明,然后在程序中删除静态对象 终止。”

还请注意,使用Meyers Singleton,“如果对象在彼此之间相互依赖,可能会陷入非常困难的境地。 终止-Singleton何时相对于其他对象消失?但是对于简单的应用程序,它可以正常工作。”

答案 15 :(得分:0)

您的代码正确,除了您没有在类外部声明实例指针。静态变量的内部类声明在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实例不需要由我们手动删除。我们在整个程序中只需要一个对象,所以在程序执行结束时,它将自动取消分配。

答案 16 :(得分:0)

这是我关于如何执行适当的单例(和其他非平凡的静态对象)的观点:https://github.com/alex4747-pub/proper_singleton

摘要:

  1. 使用静态初始化列表在正确的时间实例化单例:进入main之后且启用多线程之前
  2. 添加一些小的改进以使其易于进行单元测试。

答案 17 :(得分:0)

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

答案 18 :(得分:0)

这里是mockable singleton,使用CRTP。它依靠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();

答案 19 :(得分:0)

我想在这里展示另一个 C++ 单例示例。使用模板编程是有意义的。此外,从不可复制和非 movabe 类派生您的单例类是有意义的。代码如下:

#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 类允许您更具体地定义您的单例(有时您想移动您的单个实例)。

答案 20 :(得分:0)

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

npm install vuetify-loader -D
npm cache clean --force
npm install vuetify

答案 21 :(得分:-1)

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

答案 22 :(得分:-1)

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

答案 23 :(得分:-1)

简单的单例类,这必须是您的标头类文件

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

答案 24 :(得分:-2)

我认为您应该编写一个静态函数,其中删除静态对象。 当您要关闭应用程序时,应该调用此函数。 这将确保您没有内存泄漏。

答案 25 :(得分:-5)

如何使用这样的展示位置:

class singleton
{
    static singleton *s;
    static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
    static singleton* getinstance()
    {
        if (s == null)
        {
            s = new(buffer) singleton;
        }
        return s;
    }
};
相关问题