我有一个关于单例模式的问题。
我在单例类中看到了两个关于静态成员的案例。
首先它是一个对象,像这样
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
};
一个是指针,像这样
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;
}
};
这两种情况有什么区别?哪一个是正确的?
最佳答案
您可能应该阅读 Alexandrescu 的书。
关于本地静态,我有一段时间没有使用Visual Studio,但是在用Visual Studio 2003编译时,每个DLL都分配了一个本地静态......说一下调试的噩梦,我会记住一个尽管 :/
1. 单例的生命周期
关于单例的主要问题是生命周期管理。
如果您曾经尝试使用该对象,则需要保持活力。因此,问题来自初始化和销毁,这是 C++ 中带有全局变量的常见问题。
初始化通常是最容易纠正的事情。正如这两种方法所建议的那样,在第一次使用时进行初始化非常简单。
破坏要微妙一些。全局变量的销毁顺序与它们创建的顺序相反。所以在本地静态情况下,你实际上并没有控制事情......
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;
这里有什么问题?让我们检查构造函数和析构函数的调用顺序。
一、施工阶段:
A globalA;
被执行,A::A()
被称为 A::A()
电话B::B()
A::A()
电话C::C()
它工作正常,因为我们初始化了
B
和 C
第一次访问的实例。二、销毁阶段:
C::~C()
被调用是因为它是最后一次构造的 3 B::~B()
被称为... 哎呀,它试图访问 C
的实例! 因此,我们在破坏时有未定义的行为,嗯......
3.新战略
这里的想法很简单。全局内置函数在其他全局变量之前初始化,因此您的指针将设置为
0
在您编写的任何代码被调用之前,它确保测试:S& S::Instance() { if (MInstance == 0) MInstance = new S(); return *MInstance; }
实际上会检查实例是否正确。
然而,有人说过,这里存在内存泄漏,最糟糕的是一个永远不会被调用的析构函数。解决方案存在,并且是标准化的。打给
atexit
功能。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!!!
首先,让我们详细了解
atexit
.签名是int atexit(void (*function)(void));
,即它接受一个指向一个函数的指针,该函数不接受任何参数,也不返回任何内容。其次,它是如何工作的?好吧,就像前面的用例一样:在初始化时,它构建了一个指向函数调用的指针堆栈,并在销毁时一次清空堆栈中的一项。因此,实际上,函数是以后进先出的方式调用的。
那么这里会发生什么?
CleanUp
退出时间的方法CleanUp
方法被调用。它销毁对象(因此我们可以有效地在析构函数中工作)并将指针重置为 0
发出信号。 如果(就像在
A
、 B
和 C
的例子中)我调用一个已经被销毁的对象的实例会发生什么?好吧,在这种情况下,因为我将指针重新设置为 0
我将重建一个临时单例,然后循环重新开始。不过因为我正在清理我的堆栈,它不会存活很长时间。Alexandrescu 称它为
Phoenix Singleton
当它被摧毁后,如果需要它,它会从 Ember 中复活。另一种选择是有一个静态标志并将其设置为
destroyed
在清理过程中,让用户知道它没有得到单例的实例,例如通过返回一个空指针。我在返回指针(或引用)时遇到的唯一问题是,你最好希望没有人傻到会调用 delete
。在上面 :/4.幺半群模式
既然我们在谈论
Singleton
我想是时候介绍 Monoid
图案。本质上可以看作是Flyweight
的退化案例。模式,或使用 Proxy
在 Singleton
.Monoid
模式很简单:类的所有实例共享一个公共(public)状态。我将借此机会公开非凤凰实现:)
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;
}
有什么好处?它隐藏了状态是共享的事实,它隐藏了
Singleton
.Singleton
替换为对 Factory
的调用)delete
在你的单例的实例上,所以你真正管理状态并防止事故......无论如何你不能对恶意用户做太多! 5. 最后的话
尽管这看起来很完整,但我想指出的是,我很高兴地浏览了所有多线程问题……阅读 Alexandrescu 的现代 C++ 以了解更多信息!
关于c++ - C++中的单例模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2496918/