C ++单例devise模式

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

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

从这个声明我可以推断实例字段是在堆上启动的。 这意味着有一个内存分配。 对于我来说,完全不清楚的是什么时候内存将被释放? 还是有一个错误和内存泄漏? 这似乎是在执行中有问题。

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

看到这篇文章的一个简单的devise,以保证破坏单身的懒惰评估:
任何一个可以给我一个C ++的单例样本?

经典的懒惰评估和正确地销毁单身人士。

 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 // ======== // Dont 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 }; 

看到这篇文章关于何时使用一个单身人士:(不经常)
辛格尔顿:应该如何使用它

看到这两篇关于初始化顺序和如何应对的文章:
静态variables初始化顺序
查找C ++静态初始化顺序问题

看到这篇描述生命时间的文章:
C ++函数中静态variables的生命周期是什么?

请参阅本文讨论对单例的一些线程影响:
单例实例声明为GetInstance方法的静态variables

看到这篇文章解释了为什么双重检查locking不能在C ++上工作:
C ++程序员应该知道的所有常见的未定义行为是什么?

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

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

你可以避免内存分配。 有多种变体,在multithreading环境下都有问题。

我更喜欢这种实现(实际上,我不喜欢这样说,因为我尽可能避免使用单例):

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

它没有dynamic内存分配。

另一个不可分配的替代方法是:根据需要创build一个单例,就像C类一样:

 singleton<C>() 

运用

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

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

@洛基·阿斯塔里的回答非常好。

然而有时候需要多个静态对象来保证单例不会被破坏,直到所有使用单例的静态对象都不再需要它。

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

在接受的答案中的解决scheme有一个显着的缺点 – 在控制离开“主”function后调用单体的析构函数。 当一些依赖对象被分配在“main”内时,可能会有问题。

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

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

这是一个简单的实现。

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

只有一个对象被创build,并且这个对象引用每次都被返回。

 SingletonClass instance created! 00915CB8 00915CB8 

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

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

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

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

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

典型的实现(从我在emacs中已经有的代码中获得)将是:

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

然后依靠程序超出范围来清理。

如果你在一个需要手动清理的平台上工作,我可能会添加一个手动清理例程。

这样做的另一个问题是它不是线程安全的。 在multithreading环境中,有两个线程可以通过“if”之前有机会分配新的实例(所以都会)。 无论如何,如果依靠程序终止进行清理,这仍然不算太大的交易。

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

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

除了这里的其他讨论之外,值得一提的是,你可以具有全球性,而不限制使用情况。 例如,考虑引用计数的情况下…

 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, eg methods returning instances of ItemRef<Store_t, store_p>. */ }; 

用户现在可以为每个全局Store实例创build一个StoreWrappertypes(和全局实例),并且总是通过它们的包装实例访问商店(因此忘记了使用Store所需的模板参数的详细信息)。

我想你应该写一个静态对象被删除的静态函数。 当您即将closures应用程序时,应该调用此函数。 这将确保你没有内存泄漏。

与上面链接的论文描述了双重检查locking的缺点,即在调用对象的构造函数之前,编译器可以为对象分配内存,并设置指向分配内存地址的指针。 使用分配器手动分配内存,然后使用构造函数来初始化内存,在C ++中非常容易。 使用这个appraoch,双重检查locking工作得很好。

这里有很多答案,刚才有人问,但我想补充一下。 感谢Alan和Paul Ezust。 ( 介绍使用QtdeviseC ++模式 )

Singleton模式是一种专门的工厂,用于您希望限制所创build实例的数量或types的情况。 下面定义的CustomerFactory :: instance()方法是一个单例工厂的例子。 它会根据需要创build一个对象,但只有在第一次调用该方法时才会创build该对象。 在随后的调用中,它总是返回一个指向同一个对象的指针。

 CustomerFactory* CustomerFactory::instance(){ static CustomerFactory* retval = 0; if (retval == 0) retval = new CustomerFactory(qApp); 1 return retval; } 

1确保此对象及其所有子项在QApplication退出时被清除。

处理堆对象时不要留下内存泄漏,这一点很重要。 您可以在这方面使用QObjects的亲子关系。

如何使用这样的位置新:

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