在C ++中强制要破坏某些东西

时间:2008-11-13 20:43:48

标签: c++ destructor

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

当我退出应用程序时,我现在遇到的问题是:由于破坏的顺序不是确定性的,所以恰好单个controllerSupervisor实例在(某些)控制器本身之前被破坏,并且当它们调用时在析构函数中取消注册方法,它们在已经被破坏的对象上执行。

到目前为止我提出的唯一想法(有一个很大的冷,所以这可能并不重要)并没有将controllerSupervisor作为堆栈上的全局变量,而是在堆上(即使用new)。但是在那种情况下我没有地方可以删除它(这都是第三方类型的库)。

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

15 个答案:

答案 0 :(得分:5)

自动变量的破坏顺序(包括您在函数中使用的“普通”局部变量)的顺序与它们的创建顺序相反。所以将controllerSupervisor置于顶部。

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

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

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

答案 1 :(得分:5)

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

全书强烈推荐BTW。

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

一些建议:

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

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

答案 4 :(得分:1)

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.

答案 5 :(得分:0)

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

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

答案 6 :(得分: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();

答案 7 :(得分: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.supervisor的全局引用来避免control.前缀。

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

让主管负责破坏控制器?

答案 10 :(得分: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)
        {}
}

答案 11 :(得分:0)

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

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

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

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

答案 12 :(得分:0)

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

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

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

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

答案 13 :(得分:0)

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

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


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

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

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

答案 14 :(得分:0)

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