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
类甚至可以成为Store
的friend
(你可以有模板化的朋友!)。
如果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一个StoreWrapper
types(和全局实例),并且总是通过它们的包装实例访问商店(因此忘记了使用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; } };