管理单例析构函数

Managing a singleton destructor

本文关键字:析构函数 单例 管理      更新时间:2023-10-16

下面的小示例实现了一个我见过很多次的单例模式:

#include <iostream>
class SingletonTest {
private:
  SingletonTest() {}
  static SingletonTest *instance;
  ~SingletonTest() {
    std::cout << "Destructing!!" << std::endl;
  }
public:
  static SingletonTest *get_instance()  {
    if(!instance) instance = new SingletonTest;
    return instance;
  }
};
SingletonTest *SingletonTest::instance = 0;
int main(int argc, char *argv[]) {
  SingletonTest *s = SingletonTest::get_instance();
  return 0;
}

这样做的主要问题是,我的单例的析构函数永远不会被调用。

我可以让instance a (c++0x?)shared_ptr,它工作得很好——除了它意味着我的析构函数必须是公共的。

我可以添加一个静态的'cleanup'方法,但这会增加用户错误的可能性(例如,忘记调用它)。它也不允许在面对(未处理的)异常时进行适当的清理。

是否有一种通用的策略/模式允许惰性实例化,"自动"调用析构函数,并且仍然允许我将析构函数保持私有?

…不完全是一个直接的答案,但是对于一个注释来说太长了——为什么不这样做呢:

class SingletonTest {
private:
  SingletonTest() {}
  ~SingletonTest() {
    std::cout << "Destructing!!" << std::endl;
  }
public:
  static SingletonTest& get_instance()  {
    static SingletonTest instance;
    return instance;
  }
};

现在你有一个懒惰的单例,它将在退出时被销毁…它的可重入性不比你的代码差…

您可以编写一个取消初始化的函数,并在对象构造函数中调用atexit()来注册它。然后,当c++运行时对模块进行反初始化时,它将在main()调用你的反初始化函数之后的某个时刻。这个粗体是存在的,因为您对何时调用它的控制相当松散,这可能导致取消初始化顺序的惨败-要小心。

您总是可以将shared_ptr(或者更合适的是scoped_ptr)加为好友,以允许它访问您的私有析构函数。

注意,还有一个系统atexit()函数,它可以注册一个函数,以便在应用程序结束时调用。你可以传递一个单例的静态函数,它只对它执行delete instanance;

请注意,将将要成为单例的类与它的单例性分开通常是个好主意。特别是在测试和/或当您确实需要双元时。:)

在这里,尽量避免延迟初始化。初始化/创建你的单例,在启动时,以一个良好确定的顺序。这允许它们正确关闭并解决依赖关系,而不会出现意外。(我经历了循环的单身地狱……这比你想象的要容易…

您可以通过传入一个可以访问析构函数的deleter(例如定义为SingletonTest成员的类)来对shared_ptr使用私有析构函数。

但是,在销毁单例时需要非常小心,以确保它们在被销毁后不会被使用。为什么不直接使用一个普通的全局变量呢?

如果您将实际执行delete操作的类声明为友类(让它是shared_ptr<SingletonTest>或某种默认的删除器),则析构函数可以是私有的。虽然我觉得没有必要把它设为私有

第一个问题是:您是否希望解构单例。销毁单例可能会导致销毁顺序问题;和既然你关闭了,析构函数就不需要维护程序不变量。这大概是你唯一想运行单例模式的析构函数是,如果它管理的资源是系统无法使用的不会像临时文件那样自动清理。否则,它的最好不要调用析构函数

如果您希望调用析构函数,则有两个选项:将单个对象声明为静态局部变量instance函数,或者使用std::auto_ptr或类似的东西,而不是一个原始指针,作为指向它的指针。