Il peut être utile de parcourir les appels au constructeur dans l'ordre inverse.
B b({ A() });
Pour construire un B
le compilateur doit appeler le constructeur de B qui prend une valeur de const vector<A>&
. Ce constructeur doit à son tour faire une copie du vecteur, y compris de tous ses éléments. C'est le deuxième appel au constructeur de copie que vous voyez.
Pour construire le vecteur temporaire qui sera transmis à B
le compilateur doit invoquer le constructeur de la classe initializer_list
constructeur de std::vector
. Ce constructeur, à son tour, doit faire une copie de ce qui est contenu dans le dossier de l'utilisateur. initializer_list
* . C'est le premier appel au constructeur de copie que vous voyez.
La norme précise comment initializer_list
sont construits dans §8.5.4 [dcl.init.list]/p5 :
Un objet de type std::initializer_list<E>
est construit à partir d'une liste d'initialisation comme si l'implémentation allouait un tableau de N éléments de type const E
** où N est le nombre d'éléments de la liste d'initialisation. liste d'initialisation. Chaque élément de ce tableau est initialisé par copie avec l'élément correspondant de la liste des initialisateurs, et le std::initializer_list<E>
est construit pour faire référence à ce tableau.
La copie-initialisation d'un objet à partir d'un objet du même type utilise la résolution de surcharge pour sélectionner le constructeur à utiliser (§8.5 [dcl.init]/p17), donc avec une rvalue du même type, elle invoquera le constructeur move s'il est disponible. Ainsi, pour construire l'objet initializer_list<A>
à partir de la liste des initialisateurs entre accolades, le compilateur construira d'abord un tableau d'un const A
en passant du temporaire A
construit par A()
ce qui provoque l'appel du constructeur "move", puis la construction de l'objet initializer_list
pour faire référence à ce tableau.
Je n'arrive pas à comprendre d'où vient l'autre mouvement dans g++, par contre. initializer_list
ne sont généralement rien d'autre qu'une paire de pointeurs, et la norme exige que la copie de l'un d'entre eux n'entraîne pas la copie des éléments sous-jacents. g++ semble considérer que appeler deux fois le constructeur de mouvement lors de la création d'un initializer_list
d'un temporaire. Il a même appelle le constructeur de mouvement lors de la construction d'un initializer_list
à partir d'une lvalue.
Je pense qu'il s'agit d'une mise en œuvre littérale de l'exemple non normatif de la norme. La norme fournit l'exemple suivant :
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
L'initialisation sera implémentée d'une manière à peu près équivalente à ceci : **
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
en supposant que l'implémentation peut construire un objet initializer_list avec une paire de pointeurs.
Donc, si vous prenez cet exemple au pied de la lettre, le tableau sous-jacent à l'élément initializer_list
dans notre cas, sera construit comme si par :
const A __a[1] = { A{A()} };
ce qui entraîne deux appels au constructeur mobile parce qu'il construit un fichier temporaire A
copier-initialiser un deuxième fichier temporaire A
du premier, puis copie-initialise le membre du tableau du second temporaire. Le texte normatif de la norme, cependant, indique clairement qu'il ne devrait y avoir qu'une seule copie-initialisation, et non deux, donc cela semble être un bug.
Enfin, le premier A::A
provient directement de A()
.
Il n'y a pas grand-chose à dire sur les appels au destructeur. Tous les temporaires (quel que soit leur nombre) créés lors de la construction de b
sera détruite à la fin de la déclaration dans l'ordre inverse de la construction, et celle A
stocké dans b
sera détruite lorsque b
sort du champ d'application.
* Le site <code>initializer_list</code> des conteneurs de la bibliothèque standard sont définis comme étant équivalents à l'invocation du constructeur prenant deux itérateurs avec la valeur <code>list.begin()</code> et <code>list.end()</code> . Ces fonctions membres renvoient un <code>const T*</code> Il ne peut donc pas être déplacé. En C++14, le tableau de sauvegarde est rendu <code>const</code> Il est donc encore plus clair que vous ne pouvez pas vous en éloigner ou le changer.
** Cette réponse citait à l'origine la norme N3337 (la norme C++11 plus quelques modifications éditoriales mineures), dans laquelle le tableau a des éléments de type <code>E</code> plutôt que <code>const E</code> et le tableau dans l'exemple étant de type <code>double</code> . En C++14, le tableau sous-jacent est devenu <code>const</code> à la suite de <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1418" rel="nofollow">CWG 1418 </a>.