web-dev-qa-db-fra.com

Ordre de destruction des objets statiques en C++

Puis-je contrôler l'ordre de destruction des objets statiques? Existe-t-il un moyen d'appliquer l'ordre que je souhaite? Par exemple, spécifier d'une manière ou d'une autre que je souhaite qu'un certain objet soit détruit en dernier, ou au moins après un autre objet statique?

48
Gal Goldman

Les objets statiques sont détruits dans l'ordre inverse de la construction. Et l'ordre de construction est très difficile à contrôler. La seule chose dont vous pouvez être sûr, c'est que deux objets définis dans la même unité de compilation seront construits dans l'ordre de définition. Tout le reste est plus ou moins aléatoire.

50
Arkadiy

Réponse courte: En général, non.

Réponse légèrement plus longue: pour les objets statiques globaux dans une seule unité de traduction, l'ordre d'initialisation est de haut en bas, l'ordre de destruction est exactement inverse L'ordre entre plusieurs unités de traduction n'est pas défini.

Si vous avez vraiment besoin d'une commande spécifique, vous devez l'inventer vous-même.

11
gimpf

Les objets statiques sont détruits dans l'ordre inverse de leur construction (par exemple, le premier objet construit est détruit en dernier) et vous pouvez contrôler la séquence de construction des objets statiques à l'aide de la technique décrite à la rubrique 47 " Assurez-vous que les objets globaux sont initialisés avant d'être utilisés " dans le livre de Meyers Effective C++.

Par exemple, spécifier d'une manière ou d'une autre que je souhaite qu'un certain objet soit détruit en dernier, ou au moins après un autre objet statique?

Assurez-vous qu'il est construit avant l'autre objet statique.

Comment puis-je contrôler l'ordre de construction? toutes les statiques ne sont pas dans la même dll.

J'ignorerai (pour simplifier) ​​le fait qu'ils ne sont pas dans la même DLL.

Ma paraphrase du point 47 de Meyers (qui compte 4 pages) est la suivante. En supposant que votre global soit défini dans un fichier d’en-tête comme celui-ci ...

//GlobalA.h
extern GlobalA globalA; //declare a global

... ajouter du code à ce fichier include comme ceci ...

//GlobalA.h
extern GlobalA globalA; //declare a global
class InitA
{
  static int refCount;
public:
  InitA();
  ~InitA();
};
static InitA initA;

Cela aura pour effet que tout fichier contenant GlobalA.h (par exemple, votre fichier source GlobalB.cpp définissant votre deuxième variable globale) définira une instance statique de la classe InitA, qui sera construite avant tout le reste fichier source (par exemple, avant votre deuxième variable globale).

Cette classe InitA a un compteur de référence statique. Lorsque la première instance InitA est construite, ce qui est maintenant garanti avant que votre instance GlobalB ne soit construite, le constructeur InitA peut faire tout ce qui est à faire pour s'assurer que l'instance globalA est initialisée.

11
ChrisW

Il n’ya aucun moyen de le faire en C++ standard, mais si vous avez une bonne connaissance pratique des composants internes de votre compilateur, vous pourrez probablement le réaliser.

Dans Visual C++, les pointeurs vers les fonctions stat stat sont situés dans le segment .CRT$XI (pour init statique de type C) ou .CRT$XC (pour init statique de type C++). L'éditeur de liens collecte toutes les déclarations et les fusionne par ordre alphabétique. Vous pouvez contrôler l'ordre dans lequel l'initialisation statique se produit en déclarant vos objets dans le segment approprié à l'aide de 

#pragma init_seg

par exemple, si vous souhaitez que les objets du fichier A soient créés avant ceux du fichier B:

Fichier A.cpp:

#pragma init_seg(".CRT$XCB")
class A{}A;

Fichier B.cpp:

#pragma init_seg(".CRT$XCC")
class B{}B;

.CRT$XCB est fusionné avant .CRT$XCC. Lorsque le CRT effectue une itération via les pointeurs de fonction init stat, il rencontre le fichier A avant le fichier B.

Dans Watcom, le segment est XI et les variations sur #pragma initialize peuvent contrôler la construction:

#pragma initialize before library
#pragma initialize after library
#pragma initialize before user

... voir la documentation pour plus

4
David Mott
3
Martin York

Non, tu ne peux pas. Vous ne devriez jamais compter sur l'autre de la construction/destruction d'objets statiques.

Vous pouvez toujours utiliser un singleton pour contrôler l'ordre de construction/destruction de vos ressources globales.

0
Martin Cote

Avez-vous vraiment besoin que la variable soit initialisée avant main?

Si vous ne le faites pas, vous pouvez utiliser un langage simple pour contrôler facilement l'ordre de construction et de destruction, voir ici:

#include <cassert>

class single {
    static single* instance;

public:
    static single& get_instance() {
        assert(instance != 0);
        return *instance;
    }

    single()
    // :  normal constructor here
    {
        assert(instance == 0);
        instance = this;
    }

    ~single() {
        // normal destructor here
        instance = 0;
    }
};
single* single::instance = 0;

int real_main(int argc, char** argv) {
    //real program here...

    //everywhere you need
    single::get_instance();
    return 0;
}

int main(int argc, char** argv) {
    single a;
    // other classes made with the same pattern
    // since they are auto variables the order of construction
    // and destruction is well defined.
    return real_main(argc, argv);
}

Cela ne vous empêche PAS d'essayer réellement de créer une deuxième instance de la classe, mais si vous le faites, l'assertion échouera. D'après mon expérience, cela fonctionne bien.

0
Paolo.Bolzoni

Vous pouvez effectivement obtenir des fonctionnalités similaires en ayant un static std::optional<T> au lieu d'un T. Initialisez-le simplement comme vous le feriez avec une variable, utilisez-le avec indirection et détruisez-le en affectant std::nullopt (ou, pour le boost, boost::none).

C'est différent d'avoir un pointeur dans la mesure où il a une mémoire préallouée, et c'est ce que vous voulez, je suppose. Par conséquent, si vous le détruisez et (peut-être beaucoup plus tard) le recréez, votre objet aura la même adresse (que vous pouvez conserver) et vous ne payez pas le coût de l'allocation/désallocation dynamique à ce moment-là.

Utilisez boost::optional<T> si vous n'avez pas std::/std::experimental::.

0
lorro