C++中的单例模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2496918/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Singleton pattern in C++
提问by skydoor
I have a question about the singleton pattern.
我有一个关于单例模式的问题。
I saw two cases concerning the static member in the singleton class.
我在单例类中看到了两个关于静态成员的案例。
First it is an object, like this
首先它是一个对象,像这样
class CMySingleton
{
public:
static CMySingleton& Instance()
{
static CMySingleton singleton;
return singleton;
}
// Other non-static member functions
private:
CMySingleton() {} // Private constructor
~CMySingleton() {}
CMySingleton(const CMySingleton&); // Prevent copy-construction
CMySingleton& operator=(const CMySingleton&); // Prevent assignment
};
One is an pointer, like this
一个是指针,像这样
class GlobalClass
{
int m_value;
static GlobalClass *s_instance;
GlobalClass(int v = 0)
{
m_value = v;
}
public:
int get_value()
{
return m_value;
}
void set_value(int v)
{
m_value = v;
}
static GlobalClass *instance()
{
if (!s_instance)
s_instance = new GlobalClass;
return s_instance;
}
};
What's the difference between the two cases? Which one is correct?
这两种情况有什么区别?哪一个是正确的?
回答by Matthieu M.
You should probably read up Alexandrescu's book.
您可能应该阅读 Alexandrescu 的书。
Regarding the local static, I haven't use Visual Studio for a while, but when compiling with Visual Studio 2003, there was one local static allocated per DLL... talk about a nightmare of debugging, I'll remember that one for a while :/
关于本地静态,我有一段时间没有使用Visual Studio,但是在用Visual Studio 2003编译时,每个DLL都分配了一个本地静态......说一下调试的噩梦,我会记住一个尽管 :/
1. Lifetime of a Singleton
1. 单身人士的生命周期
The main issue about singletons is the lifetime management.
关于单身人士的主要问题是生命周期管理。
If you ever try to use the object, you need to be alive and kicking. The problem thus come from both the initialization and destruction, which is a common issue in C++ with globals.
如果您曾经尝试使用该对象,则需要保持活力。因此,问题来自初始化和销毁,这是 C++ 中带有全局变量的常见问题。
The initialization is usually the easiest thing to correct. As both methods suggest, it's simple enough to initialize on first use.
初始化通常是最容易纠正的事情。正如这两种方法所建议的那样,在第一次使用时进行初始化非常简单。
The destruction is a bit more delicate. global variables are destroyed in the reverse order in which they were created. So in the local static case, you don't actually control things....
破坏要微妙一些。全局变量的销毁顺序与它们创建的顺序相反。所以在本地静态情况下,你实际上并没有控制事情......
2. Local static
2.局部静态
struct A
{
A() { B::Instance(); C::Instance().call(); }
};
struct B
{
~B() { C::Instance().call(); }
static B& Instance() { static B MI; return MI; }
};
struct C
{
static C& Instance() { static C MI; return MI; }
void call() {}
};
A globalA;
What's the problem here ? Let's check on the order in which the constructors and destructors are called.
这里有什么问题?让我们检查构造函数和析构函数的调用顺序。
First, the construction phase:
一、施工阶段:
A globalA;
is executed,A::A()
is calledA::A()
callsB::B()
A::A()
callsC::C()
A globalA;
被执行,A::A()
被调用A::A()
电话B::B()
A::A()
电话C::C()
It works fine, because we initialize B
and C
instances on first access.
它工作正常,因为我们在第一次访问时初始化B
和C
实例。
Second, the destruction phase:
二、销毁阶段:
C::~C()
is called because it was the last constructed of the 3B::~B()
is called... oups, it attempts to accessC
's instance !
C::~C()
被调用是因为它是最后一个构造的 3B::~B()
被称为... 哎呀,它试图访问C
的实例!
We thus have undefined behavior at destruction, hum...
因此,我们在破坏时有未定义的行为,嗯......
3. The new strategy
3. 新战略
The idea here is simple. global built-ins are initialized before the other globals, so your pointer will be set to 0
before any of the code you've written will get called, it ensures that the test:
这里的想法很简单。全局内置函数在其他全局变量之前初始化,因此您的指针将在0
您编写的任何代码被调用之前设置为,它确保测试:
S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
Will actually check whether or not the instance is correct.
实际上会检查实例是否正确。
However has at been said, there is a memory leak here and worst a destructor that never gets called. The solution exists, and is standardized. It is a call to the atexit
function.
然而,有人说过,这里存在内存泄漏,最糟糕的是一个永远不会被调用的析构函数。解决方案存在,并且是标准化的。这是对atexit
函数的调用。
The atexit
function let you specify an action to execute during the shutdown of the program. With that, we can write a singleton alright:
该atexit
函数允许您指定在程序关闭期间要执行的操作。有了这个,我们就可以写一个单例了:
// in s.hpp
class S
{
public:
static S& Instance(); // already defined
private:
static void CleanUp();
S(); // later, because that's where the work takes place
~S() { /* anything ? */ }
// not copyable
S(S const&);
S& operator=(S const&);
static S* MInstance;
};
// in s.cpp
S* S::MInstance = 0;
S::S() { atexit(&CleanUp); }
S::CleanUp() { delete MInstance; MInstance = 0; } // Note the = 0 bit!!!
First, let's learn more about atexit
. The signature is int atexit(void (*function)(void));
, ie it accepts a pointer to a function that takes nothing as argument and returns nothing either.
首先,让我们更多地了解atexit
. 签名是int atexit(void (*function)(void));
,即它接受一个指向函数的指针,该函数不接受任何参数,也不返回任何内容。
Second, how does it work ? Well, exactly like the previous use case: at initialization it builds up a stack of the pointers to function to call and at destruction it empties the stack one item at a time. So, in effect, the functions get called in a Last-In First-Out fashion.
其次,它是如何工作的?好吧,就像前面的用例一样:在初始化时,它构建了一个指向函数调用的指针堆栈,并在销毁时一次清空堆栈中的一项。因此,实际上,函数是以后进先出的方式调用的。
What happens here then ?
那么这里会发生什么?
Construction on first access (initialization is fine), I register the
CleanUp
method for exit timeExit time: the
CleanUp
method gets called. It destroys the object (thus we can effectively do work in the destructor) and reset the pointer to0
to signal it.
第一次访问时的构建(初始化很好),我注册了
CleanUp
退出时间的方法退出时间:
CleanUp
方法被调用。它销毁对象(因此我们可以有效地在析构函数中工作)并重置指针以0
向它发出信号。
What happens if (like in the example with A
, B
and C
) I call upon the instance of an already destroyed object ? Well, in this case, since I set back the pointer to 0
I'll rebuild a temporary singleton and the cycle begins anew. It won't live for long though since I am depiling my stack.
如果(如带有A
,B
和的示例中C
)我调用已销毁对象的实例会发生什么情况?好吧,在这种情况下,由于我将指针放回,0
我将重建一个临时单例,然后循环重新开始。不过因为我正在清理我的堆栈,它不会存活很长时间。
Alexandrescu called it the Phoenix Singleton
as it resurrects from its ashes if it's needed after it got destroyed.
Alexandrescu 称它为“它”,Phoenix Singleton
因为它在被摧毁后如果需要,它会从灰烬中复活。
Another alternative is to have a static flag and set it to destroyed
during the clean up and let the user know it didn't get an instance of the singleton, for example by returning a null pointer. The only issue I have with returning a pointer (or reference) is that you'd better hope nobody's stupid enough to call delete
on it :/
另一种选择是有一个静态标志并destroyed
在清理期间将其设置为,并让用户知道它没有获得单例的实例,例如通过返回空指针。我返回指针(或引用)的唯一问题是你最好希望没有人愚蠢到可以调用delete
它:/
4. The Monoid Pattern
4. Monoid 模式
Since we are talking about Singleton
I think it's time to introduce the Monoid
Pattern. In essence, it can be seen as a degenerated case of the Flyweight
pattern, or a use of Proxy
over Singleton
.
既然我们在谈论Singleton
我认为是时候介绍Monoid
模式了。本质上,它可以看作是模式的退化情况Flyweight
,或者是Proxy
over的使用Singleton
。
The Monoid
pattern is simple: all instances of the class share a common state.
该Monoid
模式很简单:类共享公共状态的所有实例。
I'll take the opportunity to expose the not-Phoenix implementation :)
我将借此机会公开非凤凰实现:)
class Monoid
{
public:
void foo() { if (State* i = Instance()) i->foo(); }
void bar() { if (State* i = Instance()) i->bar(); }
private:
struct State {};
static State* Instance();
static void CleanUp();
static bool MDestroyed;
static State* MInstance;
};
// .cpp
bool Monoid::MDestroyed = false;
State* Monoid::MInstance = 0;
State* Monoid::Instance()
{
if (!MDestroyed && !MInstance)
{
MInstance = new State();
atexit(&CleanUp);
}
return MInstance;
}
void Monoid::CleanUp()
{
delete MInstance;
MInstance = 0;
MDestroyed = true;
}
What's the benefit ? It hides the fact that the state is shared, it hides the Singleton
.
有什么好处?它隐藏了状态是共享的事实,它隐藏了Singleton
.
- If you ever need to have 2 distinct states, it's possible that you'll manage to do it without changing every line of code that used it (replacing the
Singleton
by a call to aFactory
for example) - Nodoby's going to call
delete
on your singleton's instance, so you really manage the state and prevent accidents... you can't do much against malicious users anyway! - You control the access to the singleton, so in case it's called after it's been destroyed you can handle it correctly (do nothing, log, etc...)
- 如果您需要有 2 个不同的状态,您可能会设法做到这一点,而无需更改使用它的每一行代码(例如,
Singleton
通过调用 aFactory
来替换) - Nodoby 将调用
delete
您的单身人士的实例,因此您可以真正管理状态并防止发生意外……无论如何,您对恶意用户无能为力! - 您控制对单例的访问,因此如果它在被销毁后被调用,您可以正确处理它(什么都不做,记录等......)
5. Last word
5. 最后一句话
As complete as this may seem, I'd like to point out that I have happily skimmed any multithread issues... read Alexandrescu's Modern C++ to learn more!
尽管这看起来很完整,但我想指出的是,我很高兴地浏览了任何多线程问题……阅读 Alexandrescu 的现代 C++ 以了解更多信息!
回答by dash-tom-bang
Neither is more correct than the other. I would tend to try to avoid the use of Singleton in general, but when I've been faced with thinking it was the way to go, I've used both of these and they worked fine.
两者都不比另一个更正确。我一般会尽量避免使用 Singleton,但是当我认为这是要走的路时,我已经使用了这两种方法并且它们工作得很好。
One hitch with the pointer option is that it'll leak memory. On the other hand, your first example may end up getting destroyed before you're done with it, so you'll have a battle to wage regardless if you don't choose to figure out a more appropriate owner for this thing, that can create and destroy it at the right times.
指针选项的一个问题是它会泄漏内存。另一方面,您的第一个示例可能会在您完成之前就被销毁,因此无论您是否选择为这件事找到更合适的所有者,您都将面临一场战斗在正确的时间创建和销毁它。
回答by Billy ONeal
The difference is that the second one leaks memory (the singleton itself) while the first one does not. Static objects are initialized once the first time their associated method is called, and (so long as the program exits cleanly) they are destroyed before the program exits. The version with the pointer will leave the pointer allocated at program exit and memory checkers like Valgrind will complain.
不同之处在于第二个泄漏内存(单例本身)而第一个没有。静态对象在其关联方法第一次被调用时被初始化,并且(只要程序干净地退出)它们在程序退出之前被销毁。带有指针的版本将在程序退出时留下分配的指针,并且像 Valgrind 这样的内存检查器会抱怨。
Also, what's stopping somebody from doing delete GlobalClass::instance();
?
另外,是什么阻止了某人做事delete GlobalClass::instance();
?
For the above two reasons, the version using the static is the more common method and the one prescribed in the original Design Patterns book.
由于以上两个原因,使用静态的版本是更常见的方法,也是原始设计模式书中规定的方法。
回答by j_kubik
Use second approach - if you don't want to use atexit to free your object, then you can always use keeper object (eg. auto_ptr, or something self written). This might cause freeing before you are done with object, just as with first first method.
使用第二种方法——如果你不想使用 atexit 来释放你的对象,那么你总是可以使用 keeper 对象(例如 auto_ptr 或自己编写的东西)。这可能会导致在您完成对象之前释放,就像第一种方法一样。
The difference is that if you use static object, you basically have no way to check if it already got freed or not.
不同的是,如果你使用静态对象,你基本上没有办法检查它是否已经被释放。
If you use pointer, you can add additional static bool to indicate if singleton got already destroyed (as in Monoid). Then your code can always check if singleton was already destroyed, and although you might fail at what you intend to do, at least you will not get cryptic "segmentaion fault" or "access violation", and the program will avoid abnormal termination.
如果你使用指针,你可以添加额外的静态 bool 来指示单例是否已经被销毁(如在 Monoid 中)。然后你的代码可以随时检查单例是否已经被销毁,尽管你可能会失败你打算做的事情,但至少你不会得到神秘的“段错误”或“访问冲突”,并且程序将避免异常终止。
回答by Sagnik
I agree with Billy. In 2nd approach we are dynamically allocating memory from the heap using new. This memory remains always and never gets freed, unless a call to deleteis been made. Hence the Global pointer approach creates a memory leak.
我同意比利。在第二种方法中,我们使用new从堆动态分配内存。除非调用删除,否则此内存始终保留并且永远不会被释放。因此全局指针方法会造成内存泄漏。
class singleton
{
private:
static singleton* single;
singleton()
{ }
singleton(const singleton& obj)
{ }
public:
static singleton* getInstance();
~singleton()
{
if(single != NULL)
{
single = NULL;
}
}
};
singleton* singleton :: single=NULL;
singleton* singleton :: getInstance()
{
if(single == NULL)
{
single = new singleton;
}
return single;
}
int main() {
singleton *ptrobj = singleton::getInstance();
delete ptrobj;
singleton::getInstance();
delete singleton::getInstance();
return 0;
}
回答by Genie
A better approach is to create a singleton class. This also avoids the instance availability check in the GetInstance() function. This can be achieved using a function pointer.
更好的方法是创建一个单例类。这也避免了 GetInstance() 函数中的实例可用性检查。这可以使用函数指针来实现。
class TSingleton;
typedef TSingleton* (*FuncPtr) (void);
class TSingleton {
TSingleton(); //prevent public object creation
TSingleton (const TSingleton& pObject); // prevent copying object
static TSingleton* vObject; // single object of a class
static TSingleton* CreateInstance (void);
static TSingleton* Instance (void);
public:
static FuncPtr GetInstance;
};
FuncPtr TSingleton::GetInstance = CreateInstance;
TSingleton* TSingleton::vObject;
TSingleton::TSingleton()
{
}
TSingleton::TSingleton(const TSingleton& pObject)
{
}
TSingleton* TSingleton::CreateInstance(void)
{
if(vObject == NULL){
// Introduce here some code for taking lock for thread safe creation
//...
//...
//...
if(vObject == NULL){
vObject = new TSingleton();
GetInstance = Instance;
}
}
return vObject;
}
TSingleton* TSingleton::Instance(void)
{
return vObject;
}
void main()
{
TSingleton::GetInstance(); // this will call TSingleton::Createinstance()
TSingleton::GetInstance(); // this will call TSingleton::Instance()
// all further calls to TSingleton::GetInstance will call TSingleton::Instance() which simply returns already created object.
}
回答by Skeets
Your first example is more typical for a singleton. Your second example differes in that it is created on-demand.
你的第一个例子对于单身人士来说更典型。您的第二个示例的不同之处在于它是按需创建的。
However I would try to avoid using singletons in general since they are nothing more than global variables.
但是,我会尽量避免使用一般的单例,因为它们只不过是全局变量。
回答by David R Tribble
In response to the "memory leak" complaints, there is an easy fix:
针对“内存泄漏”的抱怨,有一个简单的解决方法:
// dtor
~GlobalClass()
{
if (this == s_instance)
s_instance = NULL;
}
In other words, give the class a destructor that de-initializes the hidden pointer variable when the singleton object is destructed at program termination time.
换句话说,给类一个析构函数,当程序终止时单例对象被析构时,该析构函数会解除对隐藏指针变量的初始化。
Once you've done this, the two forms are practically identical. The only significant difference is that one returns a references to a hidden object while the other returns a pointer to it.
完成此操作后,这两种形式实际上是相同的。唯一的显着区别是一个返回对隐藏对象的引用,而另一个返回指向它的指针。
Update
更新
As @BillyONeal points out, this won't work because the pointed-toobject never gets deleted. Ouch.
正如@BillyONeal 指出的那样,这将不起作用,因为指向的对象永远不会被删除。哎哟。
I hate to even think about it, but you could use atexit()
to do the dirty work. Sheesh.
我什atexit()
至不想去想它,但你可以用来做肮脏的工作。嘘。
Oh, well, never mind.
哦,好吧,别介意。