【问题标题】:A valid singleton Class ?一个有效的单例类?
【发布时间】:2026-02-05 02:15:01
【问题描述】:
class Singleton
{
 private:
     static Singleton s;
     Singleton(){}
 public:
    static Singleton *getInstance()
    {
        return &s;
    }
};

Singleton Singleton::s;

这是一个有效的单例类吗?

class Singleton
{
 private:
     static Singleton *m_instance;
     Singleton(){}
 public:
    static Singleton *getInstance()
    {
        return m_instance;
    }

};
Singleton * Singleton::m_instance = new Singleton;

.

class Singleton
{
 private:
     static Singleton *m_instance;
     Singleton(){}
 public:
    static Singleton *getInstance()
    {
        if(m_instance == NULL)
        {
            lock();
            if(m_instance == NULL)
                m_instance = new Singleton;
            unlock();
        }
        return m_instance;
    }

};
Singleton * Singleton::m_instance = NULL;

上面的三个单例类都是线程安全的,但都容易出现“静态初始化顺序惨败”,对吗?

【问题讨论】:

    标签: c++ design-patterns singleton


    【解决方案1】:

    这是一个有效的单例类吗?

    现在,编辑后的答案是肯定的,它是有效的并且它也是线程安全的,因为所有非函数范围的静态变量都是在 main() 之前构造的,而只有一个活动线程。

    C++ 标准 n3337 § 3.6.2/1 § 3.6.2/2:非局部变量的初始化

    命名的非局部变量有两大类: 静态存储持续时间(3.7.1)和具有线程存储持续时间的那些 (3.7.2)。具有静态存储持续时间的非局部变量是 初始化作为程序启动的结果。非本地 具有线程存储持续时间的变量被初始化为 线程执行的结果。在启动的每个阶段中,初始化发生如下。

    具有静态存储持续时间 (3.7.1) 或线程存储的变量 持续时间(3.7.2)应在任何其他之前被零初始化(8.5) 初始化发生。执行常量初始化:

    ——如果每个完整表达式(包括隐式转换) 出现在带有静态或线程的引用的初始化程序中 存储持续时间是一个常数表达式(5.19),参考是 绑定到指定具有静态存储持续时间的对象的左值 或临时(见 12.2);

    ——如果一个具有静态或线程存储持续时间的对象被初始化 通过构造函数调用,如果构造函数是 constexpr 构造函数, 如果所有构造函数参数都是常量表达式(包括 转换),如果在函数调用替换(7.1.5)之后, mem-initializers 中的每个构造函数调用和完整表达式 在非静态数据成员的大括号或相等初始化器中是 常量表达式;

    ——如果一个具有静态或线程存储持续时间的对象不是 由构造函数调用初始化,如果每个完整表达式 出现在其初始化程序中的是一个常量表达式。

    一起调用零初始化和常量初始化 静态初始化;所有其他初始化都是动态的 初始化。静态初始化应在任何之前执行 动态初始化发生。 (...)

    C++ 标准 n3337 § 6.7/4:声明声明

    所有块范围变量的零初始化(8.5)静态 存储持续时间(3.7.1)或线程存储持续时间(3.7.2)是 在任何其他初始化发生之前执行。持续的 具有静态存储的块范围实体的初始化(3.6.2) 持续时间,如果适用,在它的块是第一个之前执行 进入。允许实现提前执行 使用静态或线程初始化其他块范围变量 在与实现相同的条件下存储持续时间 允许使用静态或线程静态初始化变量 命名空间范围内的存储持续时间。否则这样的变量是 在控件第一次通过其声明时初始化; 这样的变量在其完成时被认为是初始化的 初始化。如果初始化通过抛出异常退出, 初始化未完成,下次再试 时间控制进入申报。 如果控制进入声明 在初始化变量的同时,并发 执行应等待初始化完成*)。 (...)

    *):

    实现不得在执行的过程中引入任何死锁 初始化器。

    但它仍然容易出现static initialization order fiascogetInstance的常用写法是:

    Singleton& getInstance()
    {
        static Singleton instance;
        return instance;
    }
    

    这样可以避免这个初始化问题。

    这是一个线程安全的单例类吗?

    在 C++11 中,上面的代码是线程安全的。在 C++03 中你可以使用

    pthread_once


    除此之外,您还应该防止复制和分配:

    Singleton( Singleton const&);      // Don't Implement
    void operator=( Singleton const&); // Don't implement
    

    【讨论】:

    • 在 C++11 对象初始化之前可以由多个线程完成
    • 上面的三个单例类都是线程安全的,但都容易出现“静态初始化顺序惨败”,对吗? @lizusek
    • 它们在 C++11 中都是线程安全的(而不是在 C++03 中),但容易出现静态初始化顺序失败
    • 非常感谢@lizusek!
    【解决方案2】:

    据我所知,它是线程安全的。但它容易受到static initialization order fiasco 的影响。

    如果一个对象试图在其构造函数中访问Singleton,并且该对象是在程序初始化期间构造的,并且此代码位于Singleton之外的另一个编译单元中,它可能会或可能不会崩溃,因为Singleton::s可能会或可能不会尚未初始化(因为跨编译单元的静态对象的初始化顺序未定义)。这是一个例子:

    // in another compilation unit, far far away
    struct Foo {
        Foo() {
            Singleton::getInstance();
        }
    };
    Foo foo;
    

    【讨论】:

    • 你能举个例子吗?
    • @lizusek,我添加了一个示例。
    • 而初始化顺序问题是C++中使用单例的主要原因之一。
    【解决方案3】:

    那是惰性初始化的 Singleton,是的。在 C++11 下是线程安全的。

    【讨论】:

    • '那是惰性初始化的 Singleton,是的……'不,不是!
    最近更新 更多