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

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

到目前为止,我想到的唯一想法(感冒了,所以这可能没有多大意义)是不要将controllerSupervisor作为堆栈上的全局变量,而是放在堆上(即使用新的)。但是在这种情况下,我没有地方可以删除它(这一切都在第三方库中)。

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

有帮助吗?

解决方案

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

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

其他提示

自动变量的破坏顺序(包括<!>;正常<!>;在函数中使用的局部变量)的顺序与它们的创建顺序相反。所以将controllerSupervisor置于顶部。

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

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

  1. 通过返回指向函数静态管理程序对象的指针,在第一次使用时使用单例模式(因为未定义不同翻译单元中的对象的初始化顺序)来完成创建。
  2. 主管通常被破坏(使用关于破坏功能中的静力学的规则)。控制器使用管理程序的静态功能取消注册。那个检查主管是否已经被破坏(检查!= 0的指针)。如果是,那么什么也没做。否则通知主管。
  3. 因为我想可能有一个没有控制器连接的主管(如果只是临时的),智能指针不能用于自动销毁主管。

在Alexandrescu的现代C ++设计(Chaper 6,Singletons)中,基本上有一章关于这个主题。他定义了一个单例类,它可以管理依赖关系,甚至可以管理单例本身。

全书强烈推荐BTW。

一些建议:

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

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

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

  

init_priority(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.

您可以根据具体情况做以下任何事情。

  1. 使用gurin建议的观察者模式。基本上,主管告知控制器它正在下降......
  2. 让主管<!>“拥有<!>”;控制器在发生故障时应对其进行破坏负责。
  3. 将控制器保存在shared_pointers中,这样无论谁最后都会进行真正的破坏。
  4. 管理(智能指针)控制器和堆栈上的主管,这将允许您确定销毁的顺序
  5. 其他......

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

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

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

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();

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

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.前缀。

协调器的主管成员在析构函数运行之前不会被销毁,因此您可以保证主管将比控制器更长。

让主管负责破坏控制器?

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

如果需要,在单身人士周围使用适当的警卫(领衔等)。

// -- 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)
        {}
}

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

只是试着抓住取消注册的电话。您不必更改大量代码,因为应用程序已经关闭它并不是什么大问题。 (或者还有其他批准关闭的命令吗?)

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

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

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

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

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

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

让cotrol主管成为一名singelton。 确保控件构造函数在构造期间(而不是后面)获得管理程序。这保证了控制主管在控制之前完全构建。在控制管理器析构函数之前,将调用析构函数。

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);
          }
 };

当有问题的变量都适合一个文件(<!> quot; translation unit <!> quot;)时,C ++标准规定了初始化/销毁的顺序。跨越多个文件的任何内容都变得不可移植。

我会提出让主管销毁每个控制器的建议。这是线程安全的,只有主管告诉任何人自己摧毁(没有人自己摧毁自己),所以没有竞争条件。你还必须避免任何死锁的可能性(提示:确保控制器一旦被告知就可以自行销毁,而不需要来自主管的其他)。


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

首先,如果我决定摧毁自己并且微秒后主管决定摧毁我并告诉我,那可能不是一种竞争条件。

其次,如果您担心这种竞争条件,您可以通过要求所有销毁请求通过主管进行修复。我想破坏自己,我要么告诉主管告诉我,要么我向主管注册这个意图。如果其他人 - 包括主管 - 希望我被摧毁,他们会通过主管这样做。

当我读到这个问题的标题时,我立刻问自己<!>“如果有一种方法可以确保任何对象最终被破坏(被破坏?),那么如果两个对象采用该方法会发生什么?QUOT <!>;

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top