C ++ Singleton设计模式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C ++ Singleton设计模式相关的知识,希望对你有一定的参考价值。
最近我碰到了C ++的Singleton设计模式的实现/实现。看起来像这样(我从现实生活中采用了它):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
从这个声明我可以推断出实例字段是在堆上启动的。这意味着存在内存分配。对我来说完全不清楚的是,什么时候内存将被解除分配?还是有漏洞和内存泄漏?好像在实施中存在问题。
我的主要问题是,如何以正确的方式实施它?
在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
它确实可能是从堆中分配的,但如果没有源,就无法知道。
典型的实现(取自我已经在emacs中的一些代码)将是:
Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
};
......然后依靠超出范围的程序进行清理。
如果您在必须手动完成清理的平台上工作,我可能会添加一个手动清理例程。
这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程可以在有机会分配新实例之前通过“if”(因此两者都可以)。如果依靠程序终止进行清理,这仍然不是什么大不了的事。
有没有人提到std::call_once
和std::once_flag
?大多数其他方法 - 包括双重检查锁定 - 都被打破了。
单例模式实现中的一个主要问题是安全初始化。唯一安全的方法是使用同步障碍来保护初始化序列。但是,这些障碍本身需要安全地启动。 std::once_flag
是保证安全初始化的机制。
除了这里的其他讨论之外,值得注意的是,您可以拥有全局性,而不会限制对一个实例的使用。例如,考虑引用计数的情况......
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
所需的模板参数的血腥细节)。
这是关于对象生命周期管理。假设您的软件中不仅仅有单例。他们依靠Logger单身人士。在应用程序销毁期间,假设另一个单例对象使用Logger记录其销毁步骤。您必须保证最后清理Logger。因此,请查看本文:http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
与上面链接的论文描述了双重检查锁定的缺点是编译器可以在调用对象的构造函数之前为对象分配内存并设置指向已分配内存的地址的指针。但是在c ++中很容易使用分配器手动分配内存,然后使用构造调用来初始化内存。使用这个appraoch,双重检查锁定工作正常。
#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);
简单的单例类,这必须是您的头类文件
#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);
我想你应该写一个静态函数,其中你的静态对象被删除。当您要关闭应用程序时,应该调用此函数。这将确保您没有内存泄漏。
如何使用这样的新位置:
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;
}
};
作为一个单身人士,你通常不希望它被破坏。
当程序终止时,它将被拆除并解除分配,这是单例的正常,期望的行为。如果你想能够明确地清理它,那么向类中添加一个静态方法是非常容易的,它允许你将它恢复到干净状态,并在下次使用它时重新分配它,但这超出了范围。 “经典”单身人士。
你可以避免内存分配。存在许多变体,在多线程环境的情况下都存在问题。
我更喜欢这种实现(实际上,我没有正确地说我更喜欢,因为我尽可能地避免单身人士):
class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
};
它没有动态内存分配。