有效的单例类 ?

A valid singleton Class ?

本文关键字:单例类 有效      更新时间:2023-10-16
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;

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

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

现在,在编辑之后,答案是肯定的,它是有效的,它也是线程安全的,因为所有非函数范围的静态变量都是在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),引用为 绑定到指定具有静态存储持续时间的对象的 lvalue 或临时(见12.2);

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

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

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

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

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

*):

实现不得在执行 初始值设定项。

但它仍然容易出现静态初始化顺序惨败。编写getInstance的常用方法是:

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

据我所知,它是线程安全的。但它容易受到静态初始化顺序惨败的影响。

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

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

这是懒惰的初始化单例,是的。它在 C++11 下是线程安全的。