【问题标题】:Forcing something to be destructed last in C++强制在 C++ 中最后销毁某些东西
【发布时间】:2010-09-22 05:45:15
【问题描述】:

我正在开发一个 C++ 应用程序,该应用程序内部有一些定期创建和销毁的控制器对象(使用新的)。这些控制器必须将自己注册到另一个对象(我们称之为controllerSupervisor),并在它们被破坏时取消注册。

我现在面临的问题是在我退出应用程序时发生的:由于破坏顺序不是确定性的,因此单个 controllerSupervisor 实例在(某些)控制器本身之前被破坏,并且当他们调用在他们的析构函数中取消注册方法,他们在一个已经被破坏的对象上这样做。

到目前为止,我提出的唯一想法(感冒了,所以这可能没什么意义)不是将 controllerSupervisor 作为全局变量放在堆栈上,而是放在堆上(即使用 new)。但是在那种情况下,我没有删除它的地方(这都在第 3 方类型的库中)。

任何关于可能选项的提示/建议将不胜感激。

【问题讨论】:

  • “第三方库”是指你不写“main”吗?
  • 表示controller和supervisor的代码在一个静态链接库中。监督者在这个库之外是不可见的,但控制器是由应用程序创建的。
  • 如果你创建了控制器,那么你可以控制它们的销毁,不是吗?
  • @Null303,我可以销毁它们,但如果例如我有“对象 x;” x.controller 在 x 的析构函数中被销毁,我无法确定是先销毁主管还是 x。

标签: c++ destructor


【解决方案1】:

自动变量(包括您在函数中使用的“正常”局部变量)的销毁顺序与它们的创建顺序相反。所以把controllerSupervisor放在最上面。

全局变量的销毁顺序也与它们的创建顺序相反,这又取决于它们的定义顺序:稍后定义的对象稍后创建。但请注意:不同 .cpp 文件(翻译单元)中定义的对象不能保证以任何定义的顺序创建。

我认为你应该考虑按照 Mike 推荐的方式使用它:

  1. 通过在首次使用时使用单例模式(因为未定义不同翻译单元中对象的初始化顺序)通过返回指向函数静态主管对象的指针来完成创建。
  2. supervisor 通常被破坏(使用关于函数中静态的破坏规则)。控制器使用监督者的静态功能取消注册。那个检查主管是否已经被破坏(检查!= 0的指针)。如果是,那么什么都不做。否则通知主管。

因为我想可能有一个没有连接控制器的监督者(如果只是临时的),智能指针不能用于自动破坏监督者。

【讨论】:

  • controllerSupervisor 是一个全局变量。控制器是使用 new 创建的。然而,controllerSupervisor 在最后一个控制器之前就消失了(这很可能是因为其中一个控制器是另一个全局对象的一部分,但这无济于事,并且由于链接,顺序是随机的)
  • @Steven:如果您不能依赖堆栈顺序,那么您需要自己管理最终确定。是否有原因 controllerSupervisor 无法清理控制器?
【解决方案2】:

在 Alexandrescu 的 Modern C++ Design(第 6 章,单例)中基本上有一整章关于这个主题。他定义了一个单例类,它可以管理依赖关系,甚至是单例本身之间。

BTW 也强烈推荐整本书。

【讨论】:

    【解决方案3】:

    您可以使用观察者模式。控制器向其主管传达它正在被销毁。主管在销毁时也会向它的孩子传达同样的信息。

    看看http://en.wikipedia.org/wiki/Observer_pattern

    【讨论】:

    • 这听起来不错。但它是线程安全的吗? (即让控制器和主管在不同的线程中)
    • 它已经看起来像一个观察者模式,他可以利用主管已经引用了它的“观察者”这一事实
    • 它应该是线程安全的,只要只有 Supervisor 销毁任何人,控制器可以在被销毁之前完成他们正在处理的任何事情,并且控制器可以在不与 Supervisor 进行任何进一步交互的情况下销毁自己 (避免死锁)。
    【解决方案4】:

    几个建议:

    • 使 controllerSupervisor 成为单例(或将其包装在您为此目的创建的单例对象中),通过返回指针的静态方法访问,然后注册对象的 dtor 可以调用静态访问器(在应用关闭且controllerSupervisor已被销毁的情况下将返回NULL)并且这些对象可以避免在这种情况下调用de-register方法。

    • 使用new 在堆上创建controllerSupervisor 并使用boost::shared_ptr<> 之类的东西来管理它的生命周期。在单例的静态访问器方法中分发shared_ptr<>

    【讨论】:

      【解决方案5】:

      GNU gcc/g++ 为非常有用的类型提供了不可移植的属性。这些属性之一是 init_priority,它定义了全局对象的构造顺序,因此,它们被破坏的相反顺序。来自男人:

      init_priority(优先级)

       In Standard C++, objects defined at namespace scope are guaranteed
       to be initialized in an order in strict accordance with that of
       their definitions _in a given translation unit_.  No guarantee is
       made for initializations across translation units.  However, GNU
       C++ allows users to control the order of initialization of objects
       defined at namespace scope with the init_priority attribute by
       specifying a relative PRIORITY, a constant integral expression
       currently bounded between 101 and 65535 inclusive.  Lower numbers
       indicate a higher priority.
      
       In the following example, `A' would normally be created before
       `B', but the `init_priority' attribute has reversed that order:
      
            Some_Class  A  __attribute__ ((init_priority (2000)));
            Some_Class  B  __attribute__ ((init_priority (543)));
      
       Note that the particular values of PRIORITY do not matter; only
       their relative ordering.
      

      【讨论】:

        【解决方案6】:

        你可以根据情况做以下任何一种。

        1. 使用 gurin 建议的观察者模式。基本上,主管通知控制器它正在下降......
        2. 让主管“拥有”控制器,并在控制器出现故障时负责销毁它们。
        3. 将控制器保存在 shared_pointers 中,这样最后宕机的人将进行真正的破坏。
        4. 管理堆栈上的(智能指针)和监督者,这将允许您确定销毁顺序
        5. 其他...

        【讨论】:

          【解决方案7】:

          您可以查看使用注册控制器的数量作为实际删除的标记。

          删除调用只是一个请求,您必须等到控制器注销。

          如前所述,这是观察者模式的一种用法。

          class Supervisor {
          public:
              Supervisor() : inDeleteMode_(false) {}
          
              void deleteWhenDone() {
                  inDeleteMode_ = true;
                  if( controllers_.empty()){
                      delete this;
                  }
              }
          
              void deregister(Controller* controller) {
                  controllers_.erase(
                      remove(controllers_.begin(), 
                                  controllers_.end(), 
                                  controller));
                  if( inDeleteMode_ && controllers_.empty()){
                      delete this;
                  }
              }
          
          
          private:
          
              ~Supervisor() {}
              bool inDeleteMode_;
              vector<Controllers*> controllers_;
          };
          
          Supervisor* supervisor = Supervisor();
          ...
          supervisor->deleteWhenDone();
          

          【讨论】:

          • 谢谢,但是我不能手动删除主管(它是在一个静态库中创建的,没有您可能认为的 main() 例程 - 所以我没有位置调用 supervisor->deleteWhenDone() 而不会破坏代码中的一些分隔)
          【解决方案8】:

          这不是很优雅,但你可以这样做:

          struct ControllerCoordinator {
              Supervisor supervisor;
              set<Controller *> controllers;
          
              ~ControllerDeallocator() {
                  set<Controller *>::iterator i;
                  for (i = controllers.begin(); i != controllers.end(); ++i) {
                      delete *i;
                  }
              }
          }
          

          新的全局:

          ControllerCoordinator control;
          

          在您构建控制器的任何地方,添加control.supervisor.insert(controller)。在您销毁的任何地方,添加control.erase(controller)。您可以通过添加对 control.supervisor 的全局引用来避免使用 control. 前缀。

          协调器的主管成员直到析构函数运行后才会被销毁,因此您可以保证主管的寿命比控制器长。

          【讨论】:

            【解决方案9】:

            让主管负责销毁控制器怎么样?

            【讨论】:

              【解决方案10】:

              好的,正如其他地方所建议的那样,将主管设为单例(或类似的受控对象,即,限定为会话)。

              如果需要,请在单例周围使用适当的防护措施(头顶等)。

              // -- client code --
              class ControllerClient {
              public:
                  ControllerClient() : 
                      controller_(NULL)
                      {
                          controller_ = Controller::create();
                      }
              
                  ~ControllerClient() {
                      delete controller_;
                  }
                  Controller* controller_;
              };
              
              // -- library code --
              class Supervisor {
              public: 
                  static Supervisor& getIt() {        
                      if (!theSupervisor ) {
                          theSupervisor = Supervisor();
                      }
                      return *theSupervisor;
                  } 
              
                  void deregister(Controller& controller) {
                      remove( controller );
                      if( controllers_.empty() ) {
                          theSupervisor = NULL;
                          delete this;
                      }       
                  }
              
              private:    
                  Supervisor() {} 
              
                  vector<Controller*> controllers_;
              
                  static Supervisor* theSupervisor;
              };
              
              class Controller {
              public: 
                  static Controller* create() {
                      return new Controller(Supervisor::getIt()); 
                  } 
              
                  ~Controller() {
                      supervisor_->deregister(*this);
                      supervisor_ = NULL;
                  }
              private:    
                  Controller(Supervisor& supervisor) : 
                      supervisor_(&supervisor)
                      {}
              }
              

              【讨论】:

                【解决方案11】:

                虽然丑陋,但这可能是最简单的方法:

                只需在 unregister 调用周围放置一个 try catch 即可。您不必更改大量代码,并且由于应用程序已经关闭,这没什么大不了的。 (或者还有其他关于关闭命令的批准吗?)

                其他人指出了更好的设计,但这个很简单。 (而且丑陋)

                在这种情况下,我也更喜欢观察者模式。

                【讨论】:

                • 好吧,在当前情况下,应用程序关闭得很好,尤其是。因为属于主管的内存仍然存在,并且错误检查代码可以防止任何非常糟糕的事情发生。所以你是对的,没有实际问题,但我担心有一天可能会出现,我宁愿现在修复它;)
                • 这是一个不错的选择——只是提供了一种可能性。相互通知是正确的做法。当控制器收到主管离开的通知时,它可以将引用设置为 null 而不会告诉它自行销毁。 (正如其他人所说)
                【解决方案12】:

                您可以使用事件来表示控制器的销毁

                在Supervisor的析构函数中添加WaitForMultipleObjects,直到所有控制器被销毁。

                在控制器的析构函数中,您可以引发控制器退出事件。

                您需要为每个控制器维护退出事件句柄的全局数组。

                【讨论】:

                • 这可行,但它假定应用程序是线程化的,并且比观察者解决方案更复杂。
                • 它实际上是线程化的,但我同意这有点矫枉过正,而且它并不专门针对 Windows 操作系统 :-)
                【解决方案13】:

                让控制主管成为单身人士。 确保控制构造函数在构造过程中(而不是后记)获得监督者。这保证了控制监督器在控制之前完全构建。因此析构函数将在控制监督析构函数之前被调用。

                class CS
                {
                    public:
                        static CS& getInstance()
                        {
                            static CS  instance;
                            return instance;
                        }
                        void doregister(C const&);
                        void unregister(C const&);
                    private:
                        CS()
                        {  // initialised
                        }
                        CS(CS const&);              // DO NOT IMPLEMENT
                        void operator=(CS const&);  // DO NOT IMPLEMENT
                 };
                
                 class C
                 {
                      public:
                          C()
                          {
                              CS::getInstance().doregister(*this);
                          }
                          ~C()
                          {
                              CS::getInstance().unregister(*this);
                          }
                 };
                

                【讨论】:

                • 我认为你应该避免这里的单例模式,你可能不知道你是否需要由不同的主管处理不同的控制器组。
                • @Null303 - 在这种情况下,您可能不希望您的控制器是静态/全局的,因此无法控制控制器生命周期的问题就消失了。
                • @Null303:如果您知道一个控件可以有不同的 CS,那么它必须在创建时注入到控件中。这意味着您可以将它作为参数传递给构造函数,这意味着它已经被创建(因此整个问题无论如何都消失了)。
                • 其实这行不通,CS在最后一个C实例之前就被破坏了。
                • 您一定在做其他事情或使用不符合标准的编译器?以上内容在符合标准的编译器上可以正常工作,并且标准保证这样做。
                【解决方案14】:

                当有问题的变量都适合一个文件(“翻译单元”)时,C++ 标准指定了初始化/销毁的顺序。跨越多个文件的任何内容都变得不可移植。

                我会支持让主管销毁每个控制器的建议。这是线程安全的,因为只有主管告诉任何人销毁自己(没有人自己销毁自己),所以没有竞争条件。您还必须避免任何可能的死锁(提示:确保控制器在被告知后可以自行销毁,而无需来自主管的任何其他操作)。


                即使控制器需要在程序结束之前被销毁(也就是说,如果控制器可能是短暂的)那么它们(或其他人)也可以使这个线程安全。

                首先,如果我决定摧毁自己,并且在几微秒后主管决定摧毁我并告诉我,这可能不是一个需要担心的竞争条件。

                其次,如果您担心这种竞争状况,您可以通过要求所有销毁请求通过 Supervisor 来解决它。我想摧毁自己我要么告诉主管告诉我,要么我向主管登记这个意图。如果其他人——包括主管——想要我被摧毁,他们会通过主管来做到这一点。

                【讨论】:

                • 这是有道理的,但是在我的情况下,控制器也会在程序终止之前被销毁(它们的寿命可能很短)。
                【解决方案15】:

                当我读到这个问题的标题时,我立即问自己“如果有一种方法可以确保任何对象最后被销毁(销毁?),那么如果两个对象采用该方法会发生什么?”

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-08-30
                  • 2014-11-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多