我已经有一些使用指针的单例类:
class Logger
{
public:
static Logger* Instance()
{
if (!m_pInstance) m_pInstance = new Logger;
return m_pInstance;
}
private:
Logger();
Logger(Logger const&);
Logger& operator=(Logger const&);
static Logger* m_pInstance;
};
但是,有一种更简单的方法,使用引用:
class Logger
{
public:
static Logger& Instance()
{
static Logger theLogger;
return theLogger;
}
private:
Logger();
Logger(Logger const&);
Logger& operator=(Logger const&);
~Logger();
};
阅读文章C++ Singleton design pattern,它警告第二种方式:
[潜在陷阱]:这种形式的单身人士可能会出现问题 因为物体的预期寿命。如果是一个单身人士 在另一个实例化中,必须敏锐地意识到这一点 析构函数调用序列。
但是我无法理解,有人能告诉我一个不好的用法,我应该避免它吗?
答案 0 :(得分:7)
两个选项确实存在问题。
典型的单身人士
class Logger
{
public:
static Logger* Instance()
{
if (!m_pInstance) m_pInstance = new Logger;
return m_pInstance;
}
private:
Logger();
Logger(Logger const&);
Logger& operator=(Logger const&);
static Logger* m_pInstance;
};
此代码不是线程安全的,因此对new Logger
的调用可能会多次发生(在不同的线程中)。它还会泄漏Logger
实例(因为它永远不会被删除),如果应该执行析构函数,这可能是一个问题。
迈耶的单身人士
class Logger
{
public:
static Logger& Instance()
{
static Logger theLogger;
return theLogger;
}
private:
Logger();
Logger(Logger const&);
Logger& operator=(Logger const&);
~Logger();
};
Meyer's Singleton的问题确实是由于对象的破坏。从main返回后,所有全局变量的析构函数将以这些全局变量构成的相反顺序运行 1 。这就好像那些全局变量是在堆栈上构建的。
如果在Logger之前构造了一个对象,并尝试在它自己的析构函数中使用它;那么它将使用已经被破坏的对象,导致未定义的行为。
1 更确切地说,它们的构造函数以相反的顺序完成。
最简单的选择是重新访问 Typical Singleton :
class Logger {
public:
static Logger& Instance() {
static Logger* L = new Logger;
return *L;
}
private:
...
};
现在这是线程安全的,但仍然泄漏。但实际上这里需要泄漏,因为它可以保证这个对象比任何其他析构函数都被调用的时间更长。 警告:如果你曾经在析构函数中做过一些事情,它将永远无法运行。
还有其他的变化,例如Alexandrescu的凤凰单身人士在它死后需要它从灰烬中回来;但是,在破坏过程中同时获得线程安全和安全行为很难实现。