这个Singleton实现有什么问题

What is wrong with this Singleton implementation?

本文关键字:什么 问题 实现 Singleton 这个      更新时间:2023-10-16

这个想法是在程序结束时删除C++中的Singleton。我们在课堂上学习了这种实现方法:

class Singleton
{
private:
    static Singleton* the_singleton;
protected:
    Singleton()
    {
        static Keeper keeper(this);
        /*CONSTRUCTION CODE*/
    }
    virtual ~Singleton()
    {
        /*DESTRUCTION CODE*/
    }
public:
    class Keeper
    {
    private:
        Singleton* m_logger;
    public:
        Keeper(Singleton* logger):m_logger(logger){}
        ~Keeper()
        {
            delete m_logger;
        }
    };
    friend class Singleton::Keeper;
    static Singleton* GetInstance();
    {
        if (!the_singleton)
            the_singleton = new Singleton();
        return the_singleton;
    }
};
Singleton* Singleton::the_singleton = NULL;

其想法是,在第一次创建Singleton时,将在Singleton的C'tor中创建一个静态Keeper对象,一旦程序结束,该Keeper将被销毁,进而销毁它所指向的Singleton实例。

现在,这个方法对我来说似乎很麻烦,所以我建议放弃keeper类,将Singleton的实例作为getInstance方法的静态对象:

<!-- language: c++ -->
class Singleton
{
protected:
    Singleton()
    {
        /*CONSTRUCTION CODE*/
    }
    ~Singleton()
    {
        /*DESTRUCTION CODE*/
    }
public:
    static Singleton &getInstance()
    {
        static Singleton instance;
        return instance;
    }
    /*OBJECT FUNCTIONALITY*/
};

这样,Singleton在对getInstance方法的第一次调用时构造,并在程序结束后销毁。不需要那种守门员级别。

我测试了它,它运行得很好——辛格尔顿号在正确的地方被建造和摧毁。然而,我的TA说这个模式是错误的,尽管他记不起它到底出了什么问题。所以我希望这里有人以前遇到过这个实现,可以告诉我它出了什么错。

这是错误的,但您的修改并没有引入问题——它们一直都存在。

目前还没有强制执行singleton属性。构造函数需要是私有的,而不是受保护的,以确保不会创建其他实例。


除此之外,原始代码在多线程场景中完全不可用。您的将从C++0x开始工作。


如果TA的keeper对象已经将全局指针重置回NULL,那么在程序清理期间,如果需要,他的实现将能够(在单线程环境中)重新创建单例。阅读C++中的Singleton模式。但这仍然是singleton的一个新实例,这几乎和在调用其析构函数后使用singleton一样出乎意料。

然而,他忽略了这一点,所以在keeper删除singleton之后,他会很高兴地在程序关闭期间使用无效指针。在您的系统中,即使对象的生存期已经结束,至少内存区域仍然有效。


当然,你会因为使用改进版本而被扣分,这只是表明课程的目的不是教你C++,而是助教对C++的误解。

它根本没有问题。IMHO,"守护者"这件事对这个应用程序来说太疯狂了。

在少数情况下,这可能是有道理的。例如,如果Singleton必须接受构造函数参数,那么静态分配它可能是不可能的。或者,如果它的构造函数可能失败,那么更简单的方法将不允许您恢复或重试。因此,在某些情况下,可能需要做一些更复杂的事情。然而,在许多情况下,这是不必要的。

"至少在你的记忆区域即使对象的生存期已结束"

这是真的,但对程序执行来说并不意味着什么——析构函数可能会使内存区域处于使用它无论如何都会失败的状态——例如释放内部缓冲区。在对象的析构函数被调用后,您永远不应该使用它——如果您想从其他静态/全局对象的析构函数中使用您的singleton,那么您不能保证这些析构函数的调用顺序。

如果您使用静态对象,而其他一些析构函数会在它被销毁后尝试使用它,那么将无法检查singleton是否仍然存在。若您使用将指针设置为NULL的keeper对象,那个么在其他析构函数中,您至少可以检查NULL指针,并决定根本不使用singleton。这样,尽管你可能会失败,但至少你不会得到神秘的"分段错误"或"访问违规",程序将避免异常终止。

编辑:您需要记住,在管理员将此字段设置为NULL后,修改您的访问器函数,使其不再创建对象——也许是额外的静态布尔,说明对象是否已被释放?