Question

Je travaille sur une application C ++ qui, en interne, contient des objets de contrôleur créés et détruits régulièrement (à l'aide de new). Il est nécessaire que ces contrôleurs s'enregistrent avec un autre objet (appelons-le controllerSupervisor) et se désenregistrent lorsqu'ils sont détruits.

Le problème auquel je suis confronté se produit lorsque je quitte l'application: l'ordre de destruction n'étant pas déterministe, il arrive que la seule instance controllerSupervisor soit détruite avant (certains) des contrôleurs eux-mêmes, et lorsqu'ils appellent le annulez la méthode dans leur destructeur, ils le font sur un objet déjà détruit.

La seule idée que j’ai eu jusqu’à présent (avoir un grand rhume, donc cela ne veut pas forcément dire grand chose) est de ne pas avoir le contrôleur contrôleur comme une variable globale sur la pile, mais plutôt sur le tas (c’est-à-dire en utilisant un nouveau). Cependant, dans ce cas, je n'ai pas d'endroit pour le supprimer (tout se trouve dans un type de bibliothèque tiers).

Tous les conseils / suggestions sur les options possibles sont les bienvenus.

Était-ce utile?

La solution

Vous pouvez utiliser le modèle Observer. Un contrôleur communique à son superviseur qu'il est en train d'être détruit. Et le superviseur communique la même chose à son enfant après sa destruction.

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

Autres conseils

L'ordre de destruction des variables automatiques (qui incluent & "normales &" des variables locales que vous utilisez dans les fonctions) est dans l'ordre inverse de leur création. Placez donc le contrôleur-contrôleur en haut.

L'ordre de destruction des globaux se situe également à l'inverse de leur création, qui dépend à son tour de l'ordre dans lequel ils sont définis: les objets définis ultérieurement sont créés ultérieurement. Attention, les objets définis dans différents fichiers .cpp (unités de traduction) ne sont pas garantis créés dans un ordre défini.

Je pense que vous devriez envisager de l'utiliser comme l'a recommandé Mike:

  1. La création est effectuée en utilisant le modèle singleton (l'ordre de initialisation des objets dans différentes unités de traduction n'étant pas défini) lors de la première utilisation, en renvoyant un pointeur sur un objet superviseur à fonction statique.
  2. Le superviseur est normalement détruit (en utilisant les règles relatives à la destruction de la statique dans les fonctions). Les contrôleurs se désenregistrent en utilisant une fonction statique du superviseur. Celui-là vérifie si le superviseur est déjà détruit (recherche d'un pointeur pour != 0). Si c'est le cas, rien n'est fait. Sinon, le superviseur est averti.

Puisque j'imagine qu'il pourrait y avoir un superviseur sans qu'un contrôleur ne soit connecté (et si seulement temporaire), un pointeur intelligent ne pouvait pas être utilisé pour détruire automatiquement le superviseur.

Il existe en gros un chapitre entier sur ce sujet dans Modern C ++ Design d’Alexandrescu (Chaper 6, Singletons). Il définit une classe singleton qui peut gérer les dépendances, même entre singletons eux-mêmes.

L'ensemble du livre est fortement recommandé aussi BTW.

Quelques suggestions:

  • transformez le controllerSupervisor en un singleton (ou l'enveloppez dans un objet singleton que vous créez à cette fin) auquel on accède via une méthode statique qui renvoie un pointeur, puis les développeurs des objets enregistrés peuvent appeler l'accesseur statique (qui dans le cas de l’arrêt de l’application et que le contrôleur du contrôleur a été détruit, il retournera NULL) et ces objets peuvent éviter d’appeler la méthode de désinscription dans ce cas.

  • créez le controllerSupervisor sur le tas en utilisant new et utilisez quelque chose comme boost::shared_ptr<> pour gérer sa durée de vie. Distribuez la shared_ptr<> dans la méthode d'accesseur statique du singleton.

GNU gcc / g ++ fournit des attributs non portables pour les types très utiles. L'un de ces attributs est init_priority , qui définit l'ordre de construction des objets globaux et, par conséquent, l'ordre inverse de leur destruction. De l'homme:

  

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.

Selon les circonstances, vous pouvez procéder de l'une des manières suivantes.

  1. Utilisez le motif Observer comme suggéré par gurin. En gros, le superviseur informe les contrôleurs qu’il est en train de tomber en panne ...
  2. Demandez au superviseur " posséder " les contrôleurs et être responsable de leur destruction quand il tombe en panne.
  3. Conservez les contrôleurs dans shared_pointers afin que celui qui tombe en dernier fasse la vraie destruction.
  4. Gérez à la fois les (pointeurs intelligents) des contrôleurs et le superviseur de la pile, ce qui vous permettra de déterminer l’ordre de destruction
  5. autres ...

Vous pouvez envisager d'utiliser le nombre de contrôleurs enregistrés comme sentinelle pour la suppression réelle.

L'appel de suppression est alors simplement une demande et vous devez attendre la désinscription des contrôleurs.

Comme mentionné, il s'agit d'une utilisation du modèle d'observateur.

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

Ce n'est pas vraiment élégant, mais vous pouvez faire quelque chose comme ça:

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

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

Nouveau global:

ControllerCoordinator control;

Chaque fois que vous construisez un contrôleur, ajoutez control.supervisor.insert(controller). Où que vous en détruisiez un, ajoutez control.erase(controller). Vous pourrez peut-être éviter le préfixe control. en ajoutant une référence globale à control.supervisor.

Le membre superviseur du coordinateur ne sera détruit qu'après l'exécution du destructeur. Vous avez donc la garantie que le superviseur survivra aux contrôleurs.

Pourquoi ne pas demander au superviseur de détruire les contrôleurs?

OK, comme suggéré ailleurs, faites du superviseur un singleton (ou un objet contrôlé de la même manière, c’est-à-dire étendu à une session).

Utilisez les protections appropriées (lecture, etc.) autour de singleton si nécessaire.

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

Bien que cela soit laid, cela pourrait être la méthode la plus simple:

vient de mettre un crochet à l’essai lors de l’annulation de l’appel. Vous n'avez pas à changer beaucoup de code et puisque l'application est déjà fermée, ce n'est pas grave. (Ou y a-t-il d'autres ratifications pour l'ordre d'arrêt?)

D'autres ont signalé de meilleurs modèles, mais celui-ci est simple. (et moche)

Je préfère également le motif observateur dans ce cas.

Vous pouvez utiliser des événements pour signaler la destruction de contrôleurs

Ajoutez WaitForMultipleObjects dans le destructeur de Supervisor qui attendra que tous les contrôleurs soient détruits.

Dans le destructeur de contrôleurs, vous pouvez déclencher un événement de sortie du contrôleur.

Vous devez gérer un tableau global de descripteurs d'événements Exit pour chaque contrôleur.

Faites du superviseur cotrol un singelton. Assurez-vous qu'un constructeur de contrôle obtient le superviseur lors de la construction (pas après les mots). Cela garantit que le superviseur du contrôle est entièrement construit avant le contrôle. Le destructeur sera appelé avant le destructeur du superviseur du contrôle.

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

La norme C ++ spécifie l’ordre d’initialisation / destruction lorsque les variables en question tiennent toutes dans un fichier (" unité de traduction "). Tout ce qui couvre plusieurs fichiers devient non portable.

J'appuie en second lieu les suggestions visant à demander au superviseur de détruire chaque contrôleur. Ceci est un fil de sécurité dans le sens où seul le superviseur dit à quiconque de se détruire (personne ne se détruit lui-même), il n'y a donc pas de condition de concurrence. Vous devez également éviter toute possibilité de blocage (astuce: assurez-vous que les contrôleurs peuvent se détruire une fois qu'ils ont été informés sans avoir besoin de quoi que ce soit du superviseur).

Il est possible de sécuriser ce fil même si le contrôleur doit être détruit avant la fin du programme (autrement dit, si les contrôleurs peuvent être de courte durée), ils (ou quelqu'un d'autre).

Premièrement, si je décide de me détruire moi-même et, quelques microsecondes plus tard, le superviseur décide de ne pas me détruire et me le dit.

Deuxièmement, si cette situation critique vous préoccupe, vous pouvez résoudre le problème en demandant, par exemple, que toutes les demandes de destruction passent par le superviseur. Je veux me détruire moi-même, je demande au superviseur de me le dire ou j'enregistre cette intention auprès du superviseur. Si quelqu'un d'autre, y compris le superviseur, veut me faire détruire, il le fait par l'intermédiaire du superviseur.

quand j'ai lu le titre de cette question, je me suis immédiatement demandé & "S'il existait un moyen qu'un objet puisse s'assurer qu'il soit détruit (détruit?) en dernier, qu'arriverait-il si deux objets adoptaient cette méthode ? "

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top