Pourquoi l’ordre de destruction de ces objects statiques locaux aux fonctions n’EST PAS l’inverse de leur ordre d’initialisation?

J’ai deux objects statiques locaux, un et deux. Le constructeur et le destructeur accèdent tous les deux à deux par GetTwo ():

#include  struct One; struct Two; const One& GetOne(); const Two& GetTwo(); struct Two { const char* value = "It's two!"; Two() { std::cout << "Two construct" << std::endl; } ~Two() { std::cout << "Two destruct" << std::endl; } }; struct One { One() { std::cout << "One construct" << std::endl; const char* twoval = GetTwo().value; std::cout << "twoval is: " << twoval << std::endl; } ~One() { std::cout << "One destruct" << std::endl; const char* twoval = GetTwo().value; std::cout << "twoval is: " << twoval << std::endl; } }; const One& GetOne() { static One one; return one; } const Two& GetTwo() { static Two two; return two; } int main(void) { GetOne(); } 

Je comstack ceci avec g ++ 4.8.4: g ++ -std = c ++ 11 [nom du fichier]

Et cela donne:

 One construct Two construct twoval is: It's two! One destruct twoval is: It's two! Two destruct 

Ils sont construits et détruits dans le même ordre! J’ai lu que pour les variables statiques de classes C ++ dans la même unité de traduction, l’ordre de destruction est toujours l’inverse de l’ordre de construction. Mais je suppose que non? Ou est-ce un comportement indéfini?

De plus, j’ai entendu dire que pour le C ++ 11, le comité C ++ avait ajouté des garanties fantaisistes concernant les variables statiques locales, telles que la sécurité des threads. S’il n’est pas indéfini, ce comportement fait-il partie de ces garanties? (Ce qui serait bien, car cela vous empêcherait de vous tirer dans le pied avec le destructeur de One en utilisant une instance détruite de Two.) Et qu’est-ce qui est garanti si GetOne et GetTwo sont dans des unités de traduction différentes?

MODIFIER:

Merci pour les commentaires jusqu’à présent, je vois maintenant qu’un object est considéré construit uniquement après le retour de son constructeur, et non lors de la première entrée; deux est donc réellement construit avant One.

De plus, j’ai essayé de lire la norme et je l’ai trouvé dans la norme C ++ 11, section 6.7, point 4:

L’initialisation à zéro (8.5) de toutes les variables de scope de bloc avec une durée de stockage statique (3.7.1) ou une durée de stockage d’unité d’exécution (3.7.2) est effectuée avant toute autre initialisation. L’initialisation constante (3.6.2) d’une entité de scope de bloc avec une durée de stockage statique, le cas échéant, est effectuée avant la première entrée de son bloc. … une telle variable est initialisée la première fois que le contrôle passe par sa déclaration; une telle variable est considérée comme initialisée à la fin de son initialisation.

Et pour la destruction, 6.7 nous indique 3.6.3, qui dit:

Si l’achèvement du constructeur ou l’initialisation dynamic d’un object avec une durée de stockage statique est séquencée avant celle d’un autre, l’achèvement du destructeur de la seconde est séquencé avant l’initiation du destructeur de la première.

Donc, si je comprends bien, pour les objects statiques locaux, leur construction est “séquencée” à l’exécution, en fonction de l’ordre dans lequel les fonctions sont appelées. Et, quelle que soit l’unité de traduction dans laquelle ils sont définis, ils seront détruits à l’inverse de cet ordre dépendant de l’exécution.

Est-ce que ça sonne bien? Cela en ferait une solution intéressante au fiasco d’initialisation d’ordre statique. Cela dit, je pense que vous pouvez toujours vous tirer une balle dans le pied avec le code ci-dessous:

 #include  struct One; struct Two; const One& GetOne(); const Two& GetTwo(); void PrintOneValue(const One& one); struct Two { Two() { std::cout << "Two construct" << std::endl; } ~Two() { std::cout << "start Two destruct" << std::endl; PrintOneValue(GetOne()); std::cout << "end Two destruct" << std::endl; } }; struct One { const char* value = "It's one!"; One() { std::cout << "start One construct" << std::endl; GetTwo(); std::cout << "end One construct" << std::endl; } ~One() { std::cout << "One destruct" << std::endl; } }; void PrintOneValue(const One& one) { std::cout << "One's value is: " << one.value << std::endl; } const One& GetOne() { static One one; return one; } const Two& GetTwo() { static Two two; return two; } int main(void) { GetOne(); } 

Quelles sorties:

 start One construct Two construct end One construct One destruct start Two destruct One's value is: It's one! end Two destruct 

Il accède aux données de One après leur destruction, donc à son comportement indéfini. Mais au moins c’est déterministe.

Le texte standard actuel en C ++ 14 [basic.start.term] est:

Si l’achèvement du constructeur ou l’initialisation dynamic d’un object avec une durée de stockage statique est séquencée avant celle d’un autre, l’achèvement du destructeur de la seconde est séquencé avant l’initiation du destructeur de la première. [Remarque: cette définition autorise la destruction simultanée. —Fin note]

Dans votre code, two sont construits pendant le constructeur de l’ one . Par conséquent, l’ achèvement du constructeur de two est séquencé, avant l’achèvement du constructeur de l’ one .

Ainsi, l’achèvement du destructeur de l’ one est séquencé – avant l’achèvement du destructeur de two , ce qui explique ce que vous voyez.

Changez votre ctor en:

  One() { std::cout << "Start One construct" << std::endl; const char* twoval = GetTwo().value; std::cout << "twoval is: " << twoval << std::endl; std::cout << "Finish One construct" << std::endl; } 

maintenant, vous verrez que Two finissent la construction avant One . Donc, Two est enregistré pour être détruit avant One , et est détruit après, parce qu'il a été construit (complètement) en premier

 Start One construct Two construct twoval is: It's two! Finish One construct One destruct twoval is: It's two! Two destruct