单例实例释放

时间:2012-01-24 01:02:59

标签: c++ memory singleton destructor

我有这样的单身结构:

// Hpp
class Root : public boost::noncopyable
{
    public:
        ~Root();

        static Root &Get();

        void Initialize();
        void Deinitialize();

    private:
        Root();  // Private for singleton purposes
        static Root *mInstance;

        Manager1 *mManager1;
        Manager2 *mManager2;
};


// Cpp
Root *Root::mInstance = nullptr;

Root::Root()
{
    mInstance = this;

    // Managers are using `mInstance` in their constructors
    mManager1 = new Manager1();
    mManager2 = new Manager2();

    mInstance->Initialize();
}

Root::~Root() { delete mManager1; delete mManager2; }

Root &Root::Get()
{
    if (mInstance == nullptr) mInstance = new Root();
    return *mInstance;
}

void Root::Deinitialize()
{
    delete mInstance;
}

以下是这个单身人士的用法:

Root::Get();
// Some code calling related to mManager1 and mManager2
Root::Get().Deinitialize();

问题是:

  • 这是安全使用这种单例结构的内存吗?
  • 如何自动删除mInstance(手动调用dtor)。因为用户可能忘记调用Deinitialize()方法。

3 个答案:

答案 0 :(得分:3)

对于退出main()时未访问单例的单线程应用程序,您可以使用一种相当简单的方法自动完成所有操作:

Root& Root::get() {
    static std::unique_ptr<Root> rc(new Root());
    return *rc;
}

此上下文中的static表示在第一次调用函数时初始化变量,然后保持put。 C ++运行时安排static变量rc在某个时刻被销毁。对于在进入main()之前启动线程的多线程应用程序,您需要一种不同的方法,以确保静态变量仅由线程初始化。

那就是说,请注意我强烈建议使用 -pattern Singleton (也称为全球数据)。上面的代码示例不构成任何形式的推荐!您想要使用单例的有效用途很少,大多数用途都不是。我见过的所有有效用途都使用不可变的Singleton。可变单例对象往往成为同步点,并倾向于像全局数据那样模糊使用数据。

答案 1 :(得分:1)

如果您不使用Visual Studio或C ++ 11,则不会有unique_ptr&lt;&gt ;.在这种情况下,你应该坚持使用boost :: scoped_ptr.hpp,因为很快就会完全弃用std :: auto_ptr。

#include <boost/scoped_ptr.hpp>
#include <iostream>

class Foo {
  Foo() { std::cout << "constructor" << std::endl; }
public:
  static Foo& Get() {
    static boost::scoped_ptr<Foo> ptr(new Foo);
    return *ptr;
  }
  ~Foo() { std::cout << "destructor" << std::endl; }
};

int main() {
  Foo& f = Foo::Get();
  // f is now valid until the program exits. 
  //Then it is destroyed before the program finishes exiting.
  std::cout << "Work here" << std::endl;
}

或者您可以编写自己的简化scoped_ptr。

template<typename _T>
class scoped_ptr {
    _T* const mPtr;
  public:
    scoped_ptr(_T* const t) : mPtr(t) {}
    ~scoped_ptr() { delete mPtr; }
    operator _T* () { return const_cast<_T*>(mPtr); }
    // add more operators like -> if you want them
};

答案 2 :(得分:1)

你是否考虑过如果你的单身人士没有被删除会发生什么?

根据定义,单例是由许多客户端对象共享的单个对象,因为客户端往往会来去。大多数情况下,只要您的过程,您的单身人士就会活着。

当您的进程关闭时,操作系统将回收大多数资源(内存,文件句柄,套接字......),分配该单例然后让它死掉是完全正常的。这是我的建议。

  1. 代码更简单。减少函数调用,降低复杂性。不需要考虑何时应该调用终止,这就是你带来的原因。
  2. 通过显式清理,随着应用程序变得更加复杂(并且它们倾向于您不断添加功能),您会引入一些其他对象在终止和销毁后尝试访问单一引用的风险。现在,不是立即被操作系统擦除的无害内存泄漏,而是一个丑陋的未处理异常对话框供用户查看。
  3. 将Get_stance()中的单例的Initialize()函数放入(如pdillon3演示)而不是让应用程序显式调用Initialize()也是一个好习惯。您没有指定此部件,如果您的项目是可执行文件,您应该可以使用现有设计。但是,如果将此代码放在DLL中,请小心。有些人认为DllMain是初始化单例对象的好地方,但事实并非如此。在DllMain期间,加载程序锁定全局关键部分被锁定,单例初始化往往会导致各种麻烦。

    现在,而不是单个界面中的3个方法,只需要一个:GetInstance(),简单明了。

    最后,正如Dietmar所说(虽然我仍然认为Singleton是一种模式,而不是反模式而GoF同意我),你应该考虑你是否真的需要一个单身人士。我经常使用它们,但只有当我别无选择时才会使用它们,而不是因为它们很方便。当替代品更加邪恶时,它们确实是最后的设计模式。不要因为方便而使用它们。