2008-11-13 14 views
6

Estoy trabajando en una aplicación de C++ que internamente tiene algunos objetos de controlador que se crean y destruyen regularmente (utilizando los nuevos). Es necesario que estos controladores se registren a sí mismos con otro objeto (llamémoslo controllerSupervisor), y que se anulen del registro cuando se destruyan.Obligando a destruir algo pasado en C++

El problema que estoy enfrentando ahora ocurre cuando salgo de la aplicación: como el orden de destrucción no es determinista, sucede que la única instancia del controladorSupervisor se destruye antes de (algunos) de los controladores, y cuando llaman el método de anulación de registro en su destructor, lo hacen sobre un objeto ya destruido.

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

Se apreciarán todos los consejos/sugerencias sobre las posibles opciones.

+0

por "tipo de biblioteca de terceros" ¿quieres decir que no escribes "main"? –

+0

Significa que el código para el controlador y el supervisor se encuentra en una biblioteca enlazada estáticamente. El supervisor no está visible fuera de esta lib, pero la aplicación crea los controladores. – Steven

+0

Si crea los controladores, entonces puede controlar su destrucción, ¿o no? – Null303

Respuesta

2

Puede usar el patrón Observer. Un controlador le comunica al supervisor que está siendo destruido. Y el Supervisor le comunica lo mismo a su hijo después de la destrucción.

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

+0

Esto suena bien. Pero, ¿es seguro para subprocesos? (es decir, deje que los controladores y el supervisor estén en diferentes hilos) – Steven

+0

Ya parece un patrón de observador, podría usar el hecho de que el supervisor ya tiene una referencia a sus "observadores" – Null303

+0

Debe ser seguro para subprocesos siempre que solo el El supervisor destruye a cualquiera, los controladores pueden terminar lo que sea que estén trabajando antes de ser destruidos, y los controladores pueden destruirse a sí mismos sin ninguna interacción adicional con el supervisor (para evitar un punto muerto). –

5

El orden de destrucción de las variables automáticas (que incluyen las variables locales "normales" que utiliza en las funciones) está en el orden inverso al de su creación. Coloque el controladorSupervisor en la parte superior.

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

Creo que se debe considerar el uso que de cómo Mike recomienda:

  1. creación se realiza utilizando el patrón Singleton (puesto que el orden de inicialización de objetos en diferentes unidades de traducción no están definidos) en el primer uso, devolviendo una puntero a un objeto supervisor de función estática.
  2. El supervisor normalmente está destruido (usando las reglas sobre destrucción de estática en funciones). los controladores eliminan el registro usando una función estática del supervisor. Eso verifica si el supervisor ya está destruido (marcando 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 solo es temporal), un puntero inteligente no podría usarse para destruir al supervisor automáticamente.

+0

controllerSupervisor es una variable global. Los controladores se crean usando nuevo. Sin embargo, el controllerSupervisor se ha ido antes que el último de los controladores (que bien puede deberse a que uno de los controladores es parte de otro objeto global, pero esto no se puede evitar y el orden es aleatorio debido al enlace) – Steven

+0

@Steven: si puedes Confíe en el orden de apilamiento, luego necesita administrar la finalización usted mismo. ¿Hay alguna razón por la cual el controllerSupervisor no pueda limpiar los controladores? – eduffy

0

Usted podría hacer cualquiera de los siguientes de acuerdo con las circunstancias.

  1. Utilice el patrón Observer como lo sugiere gurin. Básicamente, el supervisor informa a los controladores que se está bajando ...
  2. Haga que el supervisor sea "dueño" de los controladores y responsable de su destrucción cuando se apague.
  3. Mantenga los Controladores en shared_pointers para que quien sea el último en caer haga la destrucción real.
  4. gestionar tanto los punteros inteligentes (a) los controladores y el supervisor en la pila que permitirá determinar el orden de los demás destrucción
  5. ...
0

Se podría buscar en el uso del número de controladores registrados como centinela para la eliminación real.

La llamada de eliminación es simplemente una solicitud y debe esperar hasta que los controladores cancelen la inscripción.

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

Gracias, sin embargo, no puedo eliminar el supervisor manualmente (se crea dentro de una biblioteca estática que no tiene lo que usted puede pensar como una rutina main(), así que no tengo un lugar para llamar a supervisor-> deleteWhenDone () sin romper algunas separaciones en el código) – Steven

0

No es exactamente elegante, pero se puede 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; 
     } 
    } 
} 

Nueva mundial:

ControllerCoordinator control; 

todas partes que la construcción de un controlador, agregue control.supervisor.insert(controller). Donde quiera que destruyas uno, agrega control.erase(controller). Es posible que pueda evitar el prefijo control. añadiendo una referencia global a control.supervisor.

El miembro supervisor del coordinador no se destruirá hasta que se ejecute el destructor, por lo que se garantiza que el supervisor sobrevivirá a los controladores.

1

Un par de sugerencias:

  • hacer el controllerSupervisor un producto único (o lo envuelve en un objeto Singleton se crea para ese propósito) que se accede a través de un método estático que devuelve un puntero, entonces los dtors de los objetos registrados pueden llamar al acceso estático (que en el caso del apagado de la aplicación y el controladorSupervisor ha sido destruido devolverá NULL) y esos objetos pueden evitar llamar al método de anulación de registro en ese caso.

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

0

Haga que el supervisor de cotrol sea un singelton. Asegúrese de que un constructor de control obtiene el supervisor durante la construcción (no palabras posteriores). Esto garantiza que el supervisor de control esté completamente construido antes del control. Por lo tanto, se llamará al destructor antes del destructor del supervisor de control.

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

Creo que debería evitar el patrón singleton aquí, es posible que no sepa si alguna vez necesitará un grupo diferente de controladores para ser manejado por un supervisor diferente. – Null303

+0

@ Null303 - en ese caso, probablemente no desee que sus controladores sean estáticos/globales, por lo que el problema de no poder controlar la vida útil del controlador desaparece. –

+0

@ Null303: si sabe que un control puede tener diferentes CS, debe inyectarse en el control en el momento de la creación. Esto implica que potencialmente podría pasarlo como un parámetro para el constructor, lo que implica que ya está creado (por lo tanto, todo el problema desaparece de todos modos). –

5

No es básicamente un capítulo entero sobre este tema en Modern C++ Diseño de Alexandrescu (Chaper 6, Singleton). Él define una clase singleton que puede administrar dependencias, incluso entre singletons.

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

+0

Phoenix singletons por ejemplo ... –

+0

Libro ha sido ordenado :-) – Steven

0

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

0

Bien, como se sugiere en otra parte, convierta al supervisor en un singleton (u objeto similarmente controlado, es decir, con alcance a una sesión).

Use las protecciones apropiadas (direccionamiento, 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) 
     {} 
} 
0

Si bien feo este podría ser el método más simple:

sólo hay que poner un intento de captura en torno a la llamada de anular el registro. No tiene que cambiar una gran cantidad de código y, dado que la aplicación ya se está cerrando, no es un gran problema. (¿O hay otras ratificaciones para el orden de cierre?)

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

Prefiero el patrón de observador también en este caso.

+0

Bueno, en el caso actual la aplicación se apaga muy bien, esp. ya que la memoria que pertenece al supervisor todavía está presente y el código de comprobación de errores impide que ocurra algo realmente malo. Así que tienes razón, no hay ningún problema real, pero temo que algún día podría ser y prefiero arreglarlo ahora;) – Steven

+0

Esa es una buena opción, solo ofrece una posibilidad. Las notificaciones entre sí son lo correcto. Cuando un controlador recibe una notificación de que el supervisor se va, puede establecer la referencia a nulo y no decirle que se destruya a sí mismo.(como han dicho otros) – Tim

1

GNU gcc/g ++ proporciona atributos no portátiles para los 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. Desde el 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. 
0

Es posible utilizar los eventos para señalar destrucción de Controladores

Añadir WaitForMultipleObjects en destructor de Supervisor que esperar hasta que todos los controladores se destruyen .

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

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

+0

esto funciona, pero asume que la aplicación está enhebrada y también es un poco más complicada que la solución del observador. – Tim

+0

Realmente está enhebrado, pero estoy de acuerdo en que esto es un poco exagerado, además no se dirige exclusivamente al sistema operativo Windows :-) – Steven

0

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"). Todo lo que abarca más de un archivo se convierte en no portátil.

Recomendaría las sugerencias para que el supervisor destruya cada controlador. Esto es seguro para el hilo en el sentido de que solo el Supervisor le dice a alguien que se destruya a sí mismo (nadie se destruye a sí mismo), por lo que no hay ninguna condición de raza. También debería evitar las posibilidades de interbloqueo (sugerencia: asegúrese de que los controladores puedan destruirse a sí mismos una vez que se lo indiquen sin necesidad de nada del Supervisor).


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

En primer lugar, puede que no sea una condición de carrera de la que preocuparse si decido destruirme yo mismo y microsegundos más tarde, el Supervisor decide destruirme y me lo dice.

En segundo lugar, si le preocupa esta condición de carrera, puede solucionarla solicitando que todas las solicitudes de destrucción pasen por Supervisor. Quiero destruirme a mí mismo. O le digo al Supervisor que me diga o que registre esa intención con el Supervisor. Si alguien más, incluido el Supervisor, quiere que me destruyan, lo hacen a través del Supervisor.

+0

Esto tiene sentido, sin embargo en mi caso los controladores también se destruyen antes de la finalización del programa (pueden ser bastante efímero). – Steven

0

cuando leí el encabezado de esta pregunta, inmediatamente me pregunté a mí mismo "Si hubiera alguna forma de que un objeto pueda asegurar que fue destruido (¿destruido?) Al último, ¿qué pasaría si dos objetos adoptaran ese método?"