【问题标题】:destroying a singleton object销毁单例对象
【发布时间】:2011-04-16 21:35:36
【问题描述】:

销毁单例对象的最佳方法是什么?

案例 A:单线程环境
案例B:多线程环境

示例 sn-ps(如果有)将非常有帮助。

[编辑] 我没有具体的用例,我只是想了解如果必须使用单例,如何正确销毁它。据我了解,从 cmets 来看,可能有两种情况:
1. 在没有代码访问时销毁单例。(使用智能指针,它会使用 RAII 自行销毁对象)
2. 退出程序时销毁一个单例,无论是否有代码保留在单例上。 (通过在 main 退出之前删除实例来显式销毁)

【问题讨论】:

  • 最好的方法通常是完全没有任何单例;那么你不必销毁任何单例:-)
  • 删除源文件和头文件应该可以解决问题。然后想出一个不使用单例的设计。
  • 关于为什么“单身人士是邪恶的”的背景,阅读lostechies.com/blogs/scottdensmore/archive/2009/08/13/…及其前身,从那里链接。
  • 是的,单例很难使用,如果你不小心就会导致问题。但让我们回答提出的问题。如果我们想讨论应该做的单例的优点,那是自己的具体问题。
  • @jalf, Martin - 感谢您的评论。我同意,最好摆脱单例,但在某些情况下一个不能(一些已经存在的遗留代码等),所以 q 是如果必须使用所有单例,如何确保你安全地销毁它们。

标签: c++ multithreading design-patterns oop


【解决方案1】:

一开始就不要创建它!

说真的,我强烈建议您重新考虑选择单例,尤其是在多线程环境中。相反,只需在 main() 中创建一个实例,然后将其向下传递到调用层次结构到需要它的位置。

您可以使用 shared_ptr 之类的东西来确保对象一直存在,直到没有人需要它为止。

【讨论】:

  • 我确实理解使用单例的陷阱,正如你们大多数人提到的那样,应该避免使用单例,但如果必须使用单例,那么有哪些选择?将其包装在一个使用引用计数的智能指针中也是一种选择,我正在寻找可能的解决方案或解决方法。
  • 如果将实例指针存储在具有静态存储持续时间的智能指针中,那么它将在程序退出时被销毁。
  • @Als:为什么你必须使用单例?一个单例不允许让你销毁它(因为你不再有一个实例)
  • @jalf Singleton 并不意味着必须有一个实例,而是最多一个实例。
【解决方案2】:

如果您只关心在成功关闭时进行清理,也许可以使用 atexit()

【讨论】:

    【解决方案3】:

    对过去十年过度使用 Singletons 的强烈反对似乎是一种粗鲁的健康,但它们并不完全是邪恶或不合理的……编程是关于妥协和实用的,而且很难一概而论(通常;-P) .无论如何都要重新审视设计,看看你是否可以有效地摆脱它们,但如果不能 - 就这样吧。

    无论如何,如果您想了解权衡取舍,最好从阅读 Alexandrescu 的 Modern C++ Design 开始,其中有一章专门介绍 Singleton 的替代方案。基本上,您在这里问了一个愚蠢的问题,因为我们不知道您的单身人士有哪些操作限制......哪些潜在的交互,他们可能需要使用哪些资源以及它们是否可以在关闭后重新打开等等。所以,吐出来或满足于愚蠢的答案;-P。

    【讨论】:

      【解决方案4】:

      如果你要使用全局,我更喜欢这样的:

      class GlobalThing{ /* ... */ };
      
      GlobalThing *global_thing = 0;
      
      // ...
      
      int main(){
        GlobalThing gt(/* ... */);
        global_thing = >
      
        // create threads
        // ...
        // kill threads
      }
      

      这给了你:

      1. 易于识别的全局对象生命周期。
      2. 以典型的 RAII 方式进行资源清理。
      3. 以上几点意味着它可以在多线程环境中工作而无需担心锁等问题,因为在gt的生命周期之前或之后都不会存在线程。好吧,在某些环境中,您可以退出 main() 并且其他线程将继续运行,但出于各种原因,这是一种糟糕的程序架构方式。

      你还有什么需要担心的:

      1. 全局变量的初始化顺序。但是,这与Static Initialization (and Destruction) Order Fiasco 不同,因为这种技术为您提供了定义全局变量的初始化和销毁​​顺序的好处(如果您以这种方式定义它们)。
      2. 还有别的,我敢肯定。

      【讨论】:

      • 如果有任何东西在主运行之前引用了全局指针,例如:其他全局/静态对象初始化,那么“其他”的事情之一。这并不是说这些在架构上也很好,但这是在实际代码中确实发生的另一种情况......
      • 这听起来像是将 Singleton 更改为“行为良好”(我不相信我这么说)全局变量的合理方法。
      【解决方案5】:

      暂且不论这是否是个好主意。
      我们应该在一个单独的问题中做这件事!

      class S
      {
          private:
              S() {}                // private constructor
              S(S const&);          // Undefined copy constructor
              S& operator(S const&) // Undefined assignment operator
      
          public:
              static S& getInstance()
              {
                  /*
                   * It is guaranteed to be built on first use
                   * and correctly destroyed at the end of the application 
                   */
                  // Need guard for multi-threaded systems (but not on gcc)
                  MULT_THREAD_GUARD;
                  static S theOnlyInstance;
                  return theOnlyInstance;
              }
      };
      

      对象的多线程初始化是唯一真正的问题。你可以通过这两种方式来处理。

      • 您可以放置​​一个 GUARD 以确保在进行多线程构建时只有一个线程可以进入 getInstance() 方法(如果您使用 gcc,则不需要这样做,因为它会自动植入所需的代码以保证对象是只初始化一次)。
      • 另一种技术只是确保在创建任何线程之前初始化实例。为此,只需在 main 中调用 getInstance()。请注意,如果您这样做,您可能还拥有一个全局变量,因为您破坏了单例对全局变量的主要好处(延迟初始化)。并不是说全局变量比单例好得多。

      示例保护

      // Then in your platform agnostic header file
      #ifndef MULTI_THREAD
      #define MULT_THREAD_GUARD       /* Nothing needed in single threaded code */
      #else
      #if defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 3 ) && (__GNUC_MINOR__ > 1)))
      #define MULT_THREAD_GUARD        /* Nothing required for GCC 3.2 onwards */
      #elif defined(YOUR_COMPILERS_MACRO)
      #define MULT_THREAD_GUARD        do { /* Put compiler lock code here */ } while (false)
      #else
      #error "ADD MULTI Thread GUARD for you compiler here"
      #endif
      

      【讨论】:

        【解决方案6】:

        在多线程中,

        void Singleton::Destroy() 
        {
          if (instance) { 
              pthread_mutex_lock(&lock);
              if (instance) { 
                  delete instance;
                  instance = 0x0;
              }
              pthread_mutex_unlock(&lock);
          }
        }
        

        在单线程中:

        void Singleton::Destroy() 
        {
          if (instance) { 
              delete instance;
              instance = 0x0;
          }
        }
        

        【讨论】:

        • 你的意思不是'if (instance)'吗?
        • @Arpan - 谢谢!但我认为真正的问题是确保在最后调用这些销毁方法,当没有资源不再需要单例时。我正在努力为这个问题寻找好的解决方案。
        • 这可能会导致在真正的多处理器系统上出现多个单例实例,这些系统具有每个 CPU 的缓存,甚至取决于编译器如何使用寄存器。在检查 instance!=0 之前,您需要确保“instance”来自主内存。这是现代机器上互斥锁的功能之一。这个链接 (cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) 主要是关于 Java,但它也适用于 C++。
        • 不要使用静态 MySingleton 实例;这几天返回实例?
        • @filipe 谢谢,已纠正。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-11
        • 2013-03-21
        相关资源
        最近更新 更多