关于c++中的两种单例模式

About two kinds of singleton pattern in C++

本文关键字:两种 单例模式 c++ 关于      更新时间:2023-10-16

当我寻找有关c++的单例模式的信息时,我总是找到这样的例子:

class Singleton
{
    public:
        ~Singleton() {
        }
        static Singleton* getInstance()
        {
            if(instance == NULL) {
                instance = new Singleton();
            }
            return instance;
        }
    protected:
        Singleton() {
        }
    private:
        static Singleton* instance;
};
Singleton* Singleton::instance = NULL;

但是这种类型的单例似乎也同样有效:

class Singleton
{
    public:
        ~Singleton() {
        }
        static Singleton* getInstance()
        {
            return &instance;
        }
    protected:
        Singleton() {
        }
    private:
        static Singleton instance;
};
Singleton Singleton::instance;

我猜第二个单例是在程序开始时实例化的,不像第一个,但这是唯一的区别吗?

为什么我们主要发现前者?

http://www.parashift.com/c + + -faq-lite/ctors.html #常见问题- 10.14

静态初始化顺序失败是一个非常微妙和常见的问题c++中被误解的方面。不幸的是它很难被发现错误通常发生在main()开始之前。

简而言之,假设您有两个静态对象x和y,它们存在于分离源文件,例如x.p和y.p。进一步假设y对象的初始化(通常是y对象的构造函数)调用x对象上的某个方法。

就是这样。就这么简单。

悲剧的是你有50%-50%的机会死亡。如果x.cpp的编译单元恰好首先被初始化好。但是如果y.cpp的编译单元先初始化,那么y的初始化会在x的初始化之前运行你在烤面包。例如,y的构造函数可以调用x的方法对象,但是x对象还没有被构造。

您列出的第一个方法完全避免了这个问题。它被称为"首次使用时构造习惯用法"

这种方法的缺点是对象永远不会被析构。还有另一种技术可以解决这个问题,但它需要要小心使用,因为它创造了另一个(同样)的可能性令人讨厌的问题。

注意:在某些情况下,静态初始化顺序失败也可能导致适用于内置/内在类型。

第一个允许您删除实例,而第二个不允许。但是请注意,你的第一个例子不是线程安全的

这通常被称为static初始化顺序惨败。总之,文件范围内的静态实例不必在显式函数调用之前初始化,如第一个示例所示。

单例模式通常被认为是坏实践,因此经验证据(您"看到最多"的东西)在这种情况下几乎没有价值。

第一个版本使用动态分配,第二个版本使用静态分配。也就是说,第二次分配不能失败,而第一次分配可能会抛出异常。

这两个版本各有利弊,但通常你应该尝试一种完全不需要单例的不同设计。

第一个也是"lazy" -它只在需要时才会被创建。如果您的Singleton是昂贵的,这可能是您想要的。

如果你的Singleton是便宜的你可以处理未定义的静态初始化顺序(你不使用它之前main()),你不妨去第二个解决方案