陷阱:静态堆栈变量Singleton类

时间:2013-03-09 15:14:24

标签: c++ singleton

我已经有一些使用指针的单例类:

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,它警告第二种方式:

  

[潜在陷阱]:这种形式的单身人士可能会出现问题   因为物体的预期寿命。如果是一个单身人士   在另一个实例化中,必须敏锐地意识到这一点   析构函数调用序列。

但是我无法理解,有人能告诉我一个不好的用法,我应该避免它吗?

1 个答案:

答案 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的凤凰单身人士在它死后需要它从灰烬中回来;但是,在破坏过程中同时获得线程安全安全行为很难实现。