【问题标题】:RAII for singleton单例的 RAII
【发布时间】:2012-10-31 23:01:01
【问题描述】:

我有一个单例类,它的实例在类的 CPP 文件中的全局范围内初始化:

Singleton* Singleton::uniqueInstance = new Singleton();

它的头文件看起来像:

class Singleton {
public:
    static Singleton& getInstance() { return *uniqueInstance; }
    static bool destroyInstance() { delete uniqueInstance; }

private:
    //...
    //... typical singleton stuff
    static Singleton* uniqueInstance;
}; // end of class Singleton

我注意到它的析构函数在程序终止期间没有被执行,因此我添加了一个公共静态接口Singleton::destroyInstance(),以便在程序退出之前由客户端代码手动调用,例如删除。这个 sn-p 不是完整的代码,假设还有其他代码处理线程安全问题。在这种情况下,我怎样才能利用 RAII 来消除引入这样一个接口的需要呢?感谢您的建议。

【问题讨论】:

    标签: c++ design-patterns singleton


    【解决方案1】:

    不会自动调用析构函数,因为全局对象是一个指针,而不是对象本身。如果你要这样声明全局对象:

    Singleton Singleton::uniqueInstance();
    

    然后会自动调用析构函数。但是,您将无法准确控制 何时 调用构造函数和析构函数(与其他全局对象的构造和销毁相关)。如果这对您不重要,那么您可以使用上述方法。

    【讨论】:

    • 嗨,格雷格,我不太明白代码。您的意思是在单例模式上使用常规全局对象吗?
    • 是的,如果您希望自动调用对象析构函数,那么您必须使用常规的全局对象。此外,单例“模式”并不是真正的模式,它主要只是一个如何编写模式的示例。
    • 尽管他发表了评论,但我认为 Greg 在他的原始答案中并不是在谈论常规的全局对象。您仍然可以使用这种单例模式,但只需将使用指针更改为使用类的常规实例即可。
    【解决方案2】:

    单例很容易创建,除非您处于多线程环境中。如果可以,您需要确保只有一个线程试图创建您的单例(并因此销毁它)。一旦创建,它就可以被多个线程同时使用......尽管这可能不是处理这个问题的最佳方式。

    无论如何,一个非常简单的选择是:

    class MySingleton
    {
    public:
      static MySingleton& Intance() { static MySingleton M_Instance; return M_Instance; }
    private:
      MySingleton() {}
    };
    

    只要您不使用 MI 并且在全局销毁期间不使用单例,它就可以正常工作。此外,它至少不适用于 VC2003(用于为每个调用该方法的库实例化一个单例......),我不知道更新的版本,我们已经停止在 Windows 上编译了一段时间。

    现在,如果您想真正了解有关 Singleton 的更多信息:

    • 实例化和销毁问题
    • 终生变化
    • 线程安全

    Alexandrescu 在Modern C++ Design 的整章中专注于此,如果您无法访问这本书,您仍然可以阅读Loki 中的反思产生的代码。

    此外,您可以简单地使用其他设计方法。单例可能会使测试变得困难,支持依赖注入的人对此有过有趣的想法。看看Misko Hevery blog entry

    【讨论】:

    • 您忘记将 Instance 声明为静态成员
    • 哎呀,我傻了 :x 猜猜这就是我们应该尝试编译示例的原因:)
    • 将实例声明为本地静态对象适用于 MinGW。 :) 我没有 VC2003,所以无法测试。
    • 这被称为“Meyers Singleton”,根据 C++11(以及 C++11 之前的 GCC)的定义,它实际上是线程安全的。
    【解决方案3】:

    单例最常见(通常也是最好的)变体是“泄漏单例”——即根本不尝试清理实例的变体。除非在相当不寻常的情况下,这不是真正的问题,因为只有 一个 实例被泄露,并且在执行其他任何操作时,该实例无论如何都需要存在。

    如果你真的觉得有必要清理实例,一种效果相当好的方法(尽管它也有一些问题)是使用atexit() 注册一个销毁实例的函数。

    【讨论】:

    • 需要刷新和关闭在析构函数中写入的文件。 :( atexit()...当我读到凤凰单例时,它让我想起了一个乏味的问题。:P
    【解决方案4】:

    由于您打算使用 RAII,我假设您希望在退出本地范围时销毁单例。如果是这样,我想知道你为什么要使用单例。根据定义,单例应该是全局的,因此在整个执行期间都是活着的。也许在这里,常规的、基于实例的方法更适合您。

    【讨论】:

    • 换一种方式...我正在尝试修复现有的单例代码。老实说,我不太确定 RAII 是否可以帮助解决上述问题。
    【解决方案5】:

    对于单例模式,请确保您像这样定义私有构造函数,以防止客户端创建许多对象:

    class Singleton {
    public:
        static Singleton& getInstance() { 
    
          if(!uniqueInstance)
             uniqueInstance = new Singleton();
    
          return *uniqueInstance; 
        }
    
        static bool destroyInstance() { delete uniqueInstance; }
    
    private:
        Singleton() { }; //  
        static Singleton* uniqueInstance;
    };
    
    Singleton* Singleton::uniqueInstance(NULL);
    

    【讨论】:

      【解决方案6】:

      您有两种解决方案:

      你的对象可以在“main”之前构造

      ...然后将其设为全局对象:

      // header
      class Singleton {
      public:
         static Singleton& getInstance() ;
         // ...
      
      private:
          //...
      }; // end of class
      

      .

      // source
      Singleton g_singleton ;
      
      Singleton& Singleton::getInstance()
      {
          return g_singleton ;
      }
      

      正如您提到的“在全局范围内初始化”,我想这就是您想要的。

      你的对象必须在“main”之后构造

      ...然后将其放入智能指针中:

      // header
      class Singleton {
      public:
          static Singleton& getInstance()
          {
              if(uniqueInstance.get() == 0)
              {
                 uniqueInstance.reset(new Singleton()) ;
              }
      
              return *uniqueInstance;
          }
      
          private:
          //...
          //... typical singleton stuff
          static std::auto_ptr<Singleton> uniqueInstance;
      }; // end of class Singleton
      

      .

      // source
      std::auto_ptr<Singleton> Singleton::uniqueInstance ;
      

      因此,实例将在第一次使用时创建,然后在执行结束时被智能指针的析构函数销毁。

      尽管如此,您仍然有创建多线程访问的问题。这就是为什么我更喜欢第一种解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-20
        • 1970-01-01
        相关资源
        最近更新 更多