6

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

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

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

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

4

15 回答 15

5

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

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

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

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

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

于 2008-11-13T20:47:39.977 回答
5

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

顺便说一句,整本书也强烈推荐。

于 2008-11-13T21:36:37.117 回答
2

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

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

于 2008-11-13T20:51:16.133 回答
1

几个建议:

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

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

于 2008-11-13T21:04:45.030 回答
1

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.
于 2008-11-13T22:26:03.523 回答
0

您可以根据情况进行以下任何操作。

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

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

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

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

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();
于 2008-11-13T20:59:14.027 回答
0

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

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.通过添加对 control.supervisor 的全局引用来避免使用前缀。

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

于 2008-11-13T21:02:18.997 回答
0

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

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);
          }
 };
于 2008-11-13T21:20:16.793 回答
0

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

于 2008-11-13T21:52:23.873 回答
0

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

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

// -- 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)
        {}
}
于 2008-11-13T22:06:14.373 回答
0

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

只需在取消注册调用周围尝试一下。您不必更改大量代码,而且由于应用程序已经关闭,这没什么大不了的。(或者还有其他关于关闭命令的批准吗?)

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

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

于 2008-11-13T22:12:47.637 回答
0

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

在 Supervisor 的析构函数中添加 WaitForMultipleObjects,它将等待所有控制器被销毁。

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

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

于 2008-11-13T22:51:25.407 回答
0

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

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


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

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

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

于 2008-11-13T23:07:33.580 回答
0

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

于 2008-11-19T15:11:22.680 回答