单例类的私有析构函数

12

单例类必须有私有析构函数吗?


1
不,不是。你为什么问呢?谁会强迫你呢?而且问一个只有是或否的问题并不是个好主意。 - anon
2
至少这样的问题不能因为争论而被关闭。:) - sbi
@sbi: "C++是一堆老古董垃圾,我说得对吧?" ;-) - Steve Jessop
@Steve:“现在还有人使用C++吗?” - John Dibling
@Steve和@John:适当的回答应该是是或否。没有争论的余地。我是对的? :) - sbi
如果单例对象从getInstance()返回一个引用,那么为什么你还要尝试删除它呢?这样就等于让问题无效了:https://dev59.com/Q3NA5IYBdhLWcg3wVcJx#1008289 - Martin York
7个回答

17
如果单例在全局作用域实现为变量,则必须具有公共析构函数。只有公共成员可以在全局作用域中访问。
如果它被声明为其自己类中的静态成员或静态局部变量,则析构函数可以是私有的。当程序退出时,析构函数将从类作用域内调用,并且在该作用域内是可访问的。这是一种强制对象成为单例的方法。你需要强制执行吗?如果是,那么需要。这取决于你对 "强制" 的定义。
class A{
private:
    ~A() {}
public:
    static A &getGlobalA() {
        static A a2; // <- or here - better technique
        return a2;   // this is initialized upon 1st access
    };               // and destroyed on program exit

    static A a; // <- constructor, destructor accessed from here
};

A A::a; // <- but "called" from here in terms of control flow

实际的本地static是最方便的方法之一。在a2被销毁期间(全局销毁时)调用getGlobalA会产生什么效果?我担心这会导致未定义行为。 - Matthieu M.
@Matthieu m:请参考这篇文章,了解一种简单的技术来缓解这个问题:https://dev59.com/anRC5IYBdhLWcg3wUvUS#335746 - Martin York

9

这可能不是你所寻找的...但是作为参考,我使用它如下:

// .h
class Foo {
public:
    static Foo* getInstance();
    static void destroy();
private:
    Foo();
    ~Foo();

    static Foo* myInstance;
};

// .cpp
Foo* Foo::myInstance = NULL;

Foo* Foo::getInstance(){
    if (!myInstance){
        myInstance = new Foo();
    }
    return myInstance;
}
void Foo::destroy(){
    delete myInstance;
    myInstance = NULL;
}

在我的程序结尾处,我调用对象的destroy方法。正如Péter所指出的,系统会在程序结束时回收内存,因此没有真正的理由。我使用destroy的原因是当Ogre抱怨我没有释放所有分配的内存时。之后,我只是将其用作“好习惯”,因为我喜欢清理自己留下的东西。


1
当您显式提供destroy时,也会提供一个泄漏的抽象。您应该查阅Alexandrescu的“现代C++设计”并从Loki::Singleton中获得灵感。 - Matthieu M.
2
你可能希望使用atexit注册destroy。 - Michael Aaron Safyan
我从books.google.com上了解到,在销毁单例时应该在应用程序关闭时将其删除。但是我很好奇你所说的泄漏抽象是什么意思。也许我应该创建一个新问题..? :) 如果我按照Michael Aaron的建议操作,它仍然会泄漏吗? - default
4
问题在于你正在让代码存在泄漏的风险。使用单例模式的用户必须知道他们需要使用destroy方法,并将代码插入到正确的位置。与Java不同,C++允许一个优秀的开发者承担设计类的责任,使其难以被滥用(RAII特性之一)。作为开发者,你应该尽力承担这个责任,并尝试构建一个单例模式,使用户不需要了解实现细节即可正确使用它。要求显式销毁会给用户滥用类的机会(即不调用它)。 - Martin York
如果你需要在代码完成之前销毁它(即exit()无法解决问题),那么使用单例模式可能不是你想要的。 - Chris Huang-Leaver
@Chris:是的...在学校里我学到了我在这个答案中写的内容。但是在SO上的最近几个月教会了我永远不要再使用单例模式:P所以这可能并不是一个问题。无论如何,还是谢谢。 - default

8

在我看来,单例模式的析构函数应该是私有的。否则,某些人可能会调用“delete”来删除您的单例实例。我知道,通常没有人会这样做。但是,如果我们谈论卓越设计,它必须能够抵御所有可能的故意或无意的破坏。

使用现代C ++,甚至可以为静态构造对象声明私有析构函数。以下是我的Singleton代码片段:

class Singleton
{
public:
    static Singleton& GetInstance();

    // Before C++ 11 
private:
    Singleton()  {}
    ~Singleton() {}

    Singleton(const Singleton&);            // Without implementation
    Singleton& operator=(const Singleton&); // Without implementation

    // Since C++ 11
private:
    Singleton()  = default;
    ~Singleton() = default;

public:
    Singleton(const Singleton&)            = delete;
    Singleton& operator=(const Singleton&) = delete;
};

Singleton& Singleton::GetInstance()
{
    static Singleton instance;
    return instance;
}

5
所有类都有一个析构函数。如果您不创建一个,编译器会为您创建一个。所以您的问题可以改成:单例类的析构函数必须是私有的吗?
简单的答案是否定的。
更有趣的问题是:将单例类的析构函数设为私有是否是个好主意?
一般来说,是个好主意。如果您将其设为私有,则客户端代码不会意外调用析构函数。调用析构函数会使所有客户端的单例失效,因为实例将变为无效。

如果您的单例从未被复制(因为这将使其不再是单例),它永远不会超出范围,这意味着析构函数根本不会被调用(即使是意外情况下也不会)。 - Travis Gockel
3
@Travis:它可以执行 GetSingleton()->~CSingleton() - MSalters
@MSalters,但是谁在正常情况下会这样做呢?在我看来,任何显式调用析构函数且没有使用放置 new 的代码都是极其可疑的。 - Michael Aaron Safyan
3
更加通俗易懂的翻译:稍微不那么变态的代码:p = GetSingleton(),然后注释说:「妈妈告诉我一定要删除我的指针:」delete p; - Matthieu M.
单例对象不应该从getInstance()返回指针。它应该作为引用返回,这样问题就不存在了(即使在内部通过new动态分配)。如果使用静态方法/函数变量完成,则完全不需要删除的概念。 - Martin York
显示剩余2条评论

3

通常情况下,C++中的对象不会有私有析构函数。需要记住的是,Singleton表示只有一个实例,因此需要控制/防止的是构造而不是销毁。通常,单例具有一个私有构造函数,一个公共析构函数,一个私有静态实例变量和一个公共静态Singleton获取/懒惰构造函数,虽然该模式有一些变种。


1
你可以返回对你的单例实例的引用。
class Factory : public IFactory
    {
    private:
        /**
        * This class should not be instantiated through its constructor. Since, it implements 
        * Singleton pattern.
        */
        Factory();      
    public:
        virtual ~Factory();
        /**
        * Accessor method for singleton instance.
        * \note use this static method to access to operations of this class.
        */
        static IFactory& instance(){
            if(!m_instance.get()){
                m_instance.reset(new Factory());    
            }
            return static_cast<IFactory&>(*m_instance);
        }
        /**
        * \see IFactory::create
        */
        virtual boost::shared_ptr<IConnector> create();
    private:
        /* Singleton instance */
        static boost::scoped_ptr<Factory> m_instance;

    };

除了 static_cast 是不必要的(你可以从派生类隐式转换到基类),它还算不错。但是,你应该尝试理解销毁后访问的问题。 - Matthieu M.
我认为没有必要进行动态创建。只需创建一个静态函数变量,您就可以拥有相同的功能。具有优势,您可以在一定程度上控制其何时被销毁,以便在销毁后不再访问它。 - Martin York
@Martin 是的,你说得对,但这只是懒加载的一个例子。 - baris.aydinoz
使用静态函数变量。 - Martin York

1
拥有一个私有析构函数作为单例的一部分,从程序员的角度来看并不是必需的,但从设计的角度来看却是非常重要的。
它可以避免类的误用。
然而,如果你添加了一个私有析构函数,你就必须实例化你的类:
- 在函数/方法中: 因为如果你将其创建为全局变量,你就失去了使用单例的意义(创建单例是为了避免全局变量)。 - 用正确的实例化方式: 如果你的析构函数是私有的,如果你将其实例化为“经典”的局部变量,因为它无法访问它,所以你的类在程序结束时可能无法被删除。所以你必须像这样实例化它:
Singleton * potatoe = &Singleton::getInstance();

在这里,我们创建了一个名为“potatoe”的弱指针,它对应于“getInstance”函数的结果地址。其结果是,在函数结束时析构函数不会被调用。但是,因为(在“getInstance”中)变量在静态方法中被声明为“静态”,所以析构函数将在程序结束时被调用,而无需您执行任何操作。
以下是我的代码。请随意发表评论。
// "class.hpp" file
class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton S;
        return S;
    } 
private:
    Singleton();
    ~Singleton();
};


// "main.cpp" file
#include "class.hpp"

int main()
{
        Singleton * patatoe = &Singleton::getInstance();
        Singleton * tomatoe = &Singleton::getInstance();
        Singleton * salad = &Singleton::getInstance();
        return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接