Pregunta

Estoy trabajando en una aplicación C ++ que internamente tiene algunos objetos de controlador que se crean y destruyen regularmente (usando nuevos). Es necesario que estos controladores se registren con otro objeto (llamémoslo controllerSupervisor), y se anulen cuando se destruyen.

El problema al que me enfrento ahora está sucediendo cuando salgo de la aplicación: como el orden de destrucción no es determinista, sucede que la única instancia de controllerSupervisor se destruye antes que (algunos) de los propios controladores, y cuando llaman al anulan el método en su destructor, lo hacen sobre un objeto ya destruido.

La única idea que se me ocurrió hasta ahora (tener un gran resfriado, por lo que esto puede no significar mucho) no es tener el controllerSupervisor como una variable global en la pila, sino más bien en el montón (es decir, usar nuevo). Sin embargo, en ese caso no tengo un lugar para eliminarlo (todo esto está en un tipo de biblioteca de terceros).

Cualquier sugerencia / sugerencia sobre las posibles opciones sería apreciada.

¿Fue útil?

Solución

Puedes usar el patrón Observador. Un controlador le comunica a su supervisor que está siendo destruido. Y el Supervisor le comunica lo mismo a su hijo tras la destrucción.

Eche un vistazo a http://en.wikipedia.org/wiki/Observer_pattern

Otros consejos

El orden de destrucción de las variables automáticas (que incluyen " normal " variables locales que utiliza en funciones) está en el orden inverso de su creación. Así que coloque el controllerSupervisor en la parte superior.

El orden de destrucción de los globales también está en el reverso de su creación, que a su vez depende del orden en que se definen: los objetos definidos más tarde se crean más tarde. Pero cuidado: no se garantiza que los objetos definidos en diferentes archivos .cpp (unidades de traducción) se creen en ningún orden definido.

Creo que deberías considerar usarlo como Mike recomendó:

  1. La creación se realiza utilizando el patrón singleton (dado que el orden de inicialización de los objetos en diferentes unidades de traducción no están definidos) en el primer uso, devolviendo un puntero a un objeto supervisor de función estática.
  2. El supervisor normalmente se destruye (usando las reglas sobre destrucción de estática en funciones). Los controladores anulan el registro utilizando una función estática del supervisor. Ese verifica si el supervisor ya está destruido (verificando un puntero para != 0). Si es así, entonces no se hace nada. De lo contrario, se notifica al supervisor.

Como imagino que podría haber un supervisor sin un controlador conectado (y si es solo temporal), no se podría usar un puntero inteligente para destruir al supervisor automáticamente.

Básicamente, hay un capítulo completo sobre este tema en el diseño moderno de C ++ de Alexandrescu (Capítulo 6, Singletons). Define una clase singleton que puede gestionar dependencias, incluso entre los mismos singletons.

Todo el libro es muy recomendable también por cierto.

Un par de sugerencias:

  • haga que controllerSupervisor sea un singleton (o envuélvalo en un objeto singleton que cree para ese propósito) al que se accede mediante un método estático que devuelve un puntero, luego los dtors de los objetos registrados pueden llamar al accesor estático (que en el caso del cierre de la aplicación y el controlador Supervisor ha sido destruido devolverá NULL) y esos objetos pueden evitar llamar al método de cancelación de registro en ese caso.

  • crea el controllerSupervisor en el montón usando new y usa algo como boost::shared_ptr<> para administrar su vida útil. Entregue el shared_ptr<> en el método de acceso estático del singleton.

GNU gcc / g ++ proporciona atributos no portátiles para tipos que son muy útiles. Uno de estos atributos es init_priority que define el orden en que se construyen los objetos globales y, como consecuencia, el orden inverso en el que se destruyen. Del hombre:

  

init_priority (PRIORIDAD)

 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.

Puede realizar cualquiera de las siguientes acciones según las circunstancias.

  1. Use el patrón Observador como lo sugiere gurin. Básicamente, el supervisor informa a los controladores que se está cayendo ...
  2. Tener el Supervisor " propio " los controladores y ser responsables de su destrucción cuando se caiga.
  3. Mantenga los controladores en shared_pointers para que quien caiga en último lugar haga la verdadera destrucción.
  4. Administre tanto los (punteros inteligentes a) los controladores como el supervisor en la pila, lo que le permitirá determinar el orden de destrucción
  5. otros ...

Podrías considerar usar el número de controladores registrados como centinela para la eliminación real.

La llamada de eliminación es solo una solicitud y debe esperar hasta que los controladores se den de baja.

Como se mencionó, este es un uso del patrón de observador.

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

No es exactamente elegante, pero podrías hacer algo como esto:

struct ControllerCoordinator {
    Supervisor supervisor;
    set<Controller *> controllers;

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

Nuevo global:

ControllerCoordinator control;

Donde sea que construyas un controlador, agrega control.supervisor.insert(controller). Donde quiera que destruyas uno, agrega control.erase(controller). Es posible que pueda evitar el prefijo control. agregando una referencia global a control.supervisor.

El miembro supervisor del coordinador no se destruirá hasta después de que se ejecute el destructor, por lo que tiene la garantía de que el supervisor sobrevivirá a los controladores.

¿Qué tal si el supervisor se encarga de destruir los controladores?

Ok, como se sugiere en otra parte, haga que el supervisor sea un singleton (u objeto controlado de manera similar, es decir, con un alcance para una sesión).

Use los protectores adecuados (encabezado, etc.) alrededor de singleton si es necesario.

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

Aunque feo, este podría ser el método más simple:

simplemente intente atrapar la llamada de cancelación de registro. No tiene que cambiar mucho código y, dado que la aplicación ya se está cerrando, no es gran cosa. (¿O hay otras ratificaciones para el orden de cierre?)

Otros han señalado mejores diseños, pero este es simple. (y feo)

También prefiero el patrón de observador en este caso.

Puede usar Eventos para indicar la destrucción de los Controladores

Agregue WaitForMultipleObjects en el destructor de Supervisor que esperará hasta que se destruyan todos los controladores.

En el destructor de controladores, puede provocar el evento de salida del controlador.

Necesita mantener una matriz global de controladores de eventos de salida para cada controlador.

Convierta al supervisor de cotrol en un singelton. Asegúrese de que un constructor de control obtenga el supervisor durante la construcción (no después). Esto garantiza que el supervisor de control esté completamente construido antes del control. Thuse se llamará al destructor antes que el supervisor de control destructor.

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

El estándar C ++ especifica el orden de inicialización / destrucción cuando todas las variables en cuestión caben en un archivo (" unidad de traducción "). Cualquier cosa que abarque más de un archivo se vuelve no portátil.

Me gustaría secundar las sugerencias para que el Supervisor destruya cada controlador. Esto es seguro en el sentido de que solo el Supervisor le dice a alguien que se destruya a sí mismo (nadie se destruye solo), por lo que no hay condición de carrera. También deberías evitar cualquier posibilidad de punto muerto (pista: asegúrate de que los controladores puedan destruirse a sí mismos una vez que se les haya indicado sin necesidad de nada más del Supervisor).


Es posible hacer que este hilo sea seguro incluso si el controlador necesita ser destruido antes del final del programa (es decir, si los controladores pueden ser de corta duración), entonces ellos (o alguien más).

Primero, puede que no sea una condición de carrera preocuparse si decido destruirme a mí mismo y microsegundos después, el Supervisor decide destruirme y me lo dice.

Segundo, si está preocupado por esta condición de carrera, puede solucionarlo, por ejemplo, exigiendo que todas las solicitudes de destrucción pasen por Supervisor. Quiero destruirme a mí mismo o le digo al Supervisor que me lo diga o registro esa intención con el Supervisor. Si alguien más, incluido el Supervisor, quiere que me destruyan, lo hacen a través del Supervisor.

cuando leí el encabezado de esta pregunta, inmediatamente me pregunté a mí mismo & "; si hubiera alguna forma de que cualquier objeto pudiera garantizar que fuera destruido (¿destruido?) al final, entonces, ¿qué pasaría si dos objetos adoptaran ese método? ? "

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top