41 votes

Durée de vie des objets temporaires lors de l'initialisation de la liste

J'ai toujours supposé, que les objets temporaires vivre jusqu'à la fin d'une expression. Voici cependant une curieuse différence entre les initialisations d'un std::vector et un tableau.

Veuillez considérez le code suivant:

#include <iostream>
#include <vector>

struct ID{ 
  static int cnt;
  // the number of living object of class ID at the moment of creation:  
  int id;

  ID():id(++cnt){}

  ~ID(){
     cnt--;
  }
};

int ID::cnt=0;

int main(){

  int arr[]{ID().id, ID().id};
  std::vector<int> vec{ID().id, ID().id};

  std::cout<<" Array: "<<arr[0]<<", "<<arr[1]<<"\n";
  std::cout<<" Vector: "<<vec[0]<<", "<<vec[1]<<"\n";
}

La sortie de ce programme est un peu (au moins pour moi) inattendu:

 Array: 1, 1
 Vector: 1, 2

Cela signifie que les objets temporaires sont vivants au cours de l'ensemble de l'initialisation de l' std::vector , mais ils sont créés et détruits l'un après l'autre dans le cas d'un tableau. J'attendrais la temporaires à vivre jusqu'à ce que la pleine expression int arr[]{ID().id, ID().id}; est terminée.

La norme mentionne qu'une seule exception concernant la durée de vie des objets temporaires et de l'initialisation de tableaux (12.2). Cependant je ne comprends pas son sens et ne sais pas pourquoi il est appliqué dans ce cas particulier:

Il y a deux contextes dans lesquels temporaires sont détruits dans un autre point qu'à la fin de l'expression. Le premier contexte c'est quand un constructeur par défaut est appelé à initialiser un élément d'une tableau. Si le constructeur a un ou plusieurs arguments par défaut, l' la destruction de chaque temporaire créé dans un argument par défaut est séquencée avant la construction de la prochaine élément de tableau, le cas échéant.


Aperçu des résultats avec différents compilateurs (MSVS résultat est un curtesy de NathanOliver):

             Array    Vector
clang 3.8    1, 2      1, 2
g++ 6.1      1, 1      1, 2
icpc 16      1, 1      1, 2
MSVS 2015    1, 1      1, 2

Comme ecatmur a souligné, pour l'ensemble de l'initialisation de chaque élément de l'arc-boutée-init-liste est une expression, donc le code suivant

  struct S{
      int a;
      int b;
  } s{ID().id, ID().id};
  std::cout<<" Struct: "<<s.a<<", "<<s.b<<"\n";

doit imprimer Struct 1, 1 à la console. C'est exactement ce que le programme compilé par g++. Cependant, clang semble avoir un bug - le programme qui imprime Struct 1, 2.


Un bug a été signalé à clang: https://llvm.org/bugs/show_bug.cgi?id=29080

18voto

ecatmur Points 64173

C'est la question centrale 1343 "le Séquençage de la non-initialisation de classe", qui a été accepté comme un Rapport de Défaut en novembre 2016 en papier P0570R0. La résolution proposée est une partie de C++17 mais donc pas partie du C++14, donc (sauf si le comité décide de publier un rectificatif à C++14) c'est un point de différence entre le C++17 et C++14.

C++14

Le bon de sortie selon les règles du C++14 Standard est - 1, 1 pour la matrice et de l' 1, 2 pour le vecteur; c'est parce que la construction d'un vecteur (y compris à partir d'un arc-boutée-init-liste) nécessite un appel à un constructeur lors de la construction d'un tableau n'est pas.

La langue qui régit ce est dans [intro.exécution]:

10 - Un plein-expression est une expression qui n'est pas une sous-expression d'une autre expression. [...] Si une construction du langage est défini de manière à produire un appel implicite à une fonction, un usage de la construction du langage est considéré comme une expression aux fins de la présente définition. [...]

C'est très bien comme un haut niveau d'ensemble, mais il laisse en suspens quelques questions:

  • Précisément ce qui de langages de compte comme la construction la production d'un appel implicite à une fonction;
  • Ce qui compte comme un appel implicite à une fonction; sans doute un appel à un constructeur, c'est un appel d'une fonction, mais un constructeur qui est en défaut ou définie en tant que par défaut?

Un tableau est un ensemble, afin de est initialisé à partir d'un arc-boutée-init-liste selon [dcl.init.aggr]; autrement dit, chaque élément est initialisé directement à partir de l'élément correspondant de la liste, donc il n'est pas implicite de l'appel de fonction (au moins ne correspondant pas à l'ensemble de l'initialisation). Au niveau de la syntaxe, à l'intérieur d'un initialiseur ([dcl.init]/1) à l'aide d'un arc-boutée-init-liste comme le corset ou égal initialiseur, les expressions sont les expressions contenues dans les accolades et séparés par des virgules. À la fin de chaque expression, les destructeurs de temporaires sont nécessaires pour exécuter aucun des trois contextes mentionnés dans [classe.temporaire] sont le cas ici.

Le cas de l'initialisation d'un vecteur est différente, puisque vous êtes à l'aide de l' initializer_list constructeur, de sorte qu'un appel implicite d'une fonction (c'est à dire l' initializer_list constructeur) se produit, ce qui signifie qu'il y a un implicite pleine expression qui entoure l'ensemble de l'initialisation, de sorte que les temporaires sont détruits que lors de l'initialisation du vecteur complète.

Point de prêter à confusion, [dcl.init.liste] dit que votre code est "à peu près équivalent à:

const int __a[2] = {int{ID().id}, int{ID().id}};  // #1
std::vector<int> vec(std::initializer_list<int>(__a, __a + 2));

Cependant, ce qui doit être lu dans son contexte - par exemple, la matrice de la sauvegarde de l' initializer_list a vie délimitée par l'initialisation du vecteur.

C'était beaucoup plus clair en C++03, qui avait dans [intro.exécution]:

13 - [Remarque: certains contextes, en C++ à cause de l'évaluation d'une pleine expression qui résulte d'une construction syntaxique autre que l'expression de (5.18). Par exemple, dans les 8.5 une syntaxe pour l' initialiseurest ( expression-list ) mais la construction résultante est un appel de fonction à une fonction de constructeur avec une expression de la liste comme un argument liste; tel un appel de fonction est une expression. Par exemple, dans les 8.5, une autre syntaxe pour l'initialiseurest = initializer-clause mais encore une fois la construction résultante peut être un appel de fonction à une fonction de constructeur avec une affectation d'expression comme argument; encore une fois, l'appel de fonction est une expression. ]

Ce paragraphe est frappé dans son intégralité à partir de C++11; c'est par la résolution de GTC 392. La confusion qui en résulte est sans doute pas destiné.

C++17

Après P0570R0, [intro.exécution] indique qu'une pleine expression est: [...]

  • un init-déclaration ([dcl.decl]) [ ... ], y compris la constituante expressions de l'initialiseur, ou [...]
  • une expression qui n'est pas une sous-expression d'une autre expression, et qui n'est pas partie d'une expression.

Donc, en C++17, la pleine expression est - arr[]{ID().id, ID().id} et vec{ID().id, ID().id} respectivement, et le bon de sortie est - 1, 2 , dans chaque cas, depuis la destruction de la première temporaire ID est reportée à la fin de l'expression.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X