I. Problèmes▲
I-A. Question Junior▲
Quelle est la différence entre « initialisation directe » et « initialisation par copie » ?
Astuce : reportez-vous à un gotw antérieur.
I-B. Question Guru▲
Lesquels des cas suivants utilisent l'initialisation directe et lesquels utilisent l'initialisation par copie ?
struct
T : S {
T() : S(1
), // initialisation de base
x(2
) {}
// initialisation de membre
X x;
}
;
T f( T t ) {
// passage d'un argument de fonction
return
t; // retour d'une valeur
}
S s;
T t;
S&
r =
t;
reinterpret_cast
<
S>
(t); // effectuer un reinterpret_cast
static_cast
<
S>
(t); // effectuer un static_cast
dynamic_cast
<
T&>
(r); // effectuer un dynamic_cast
const_cast
<
const
T&>
(t); // effectuer un const_cast
try
{
throw
T(); // lancer une exception
}
catch
( T t ) {
// traiter une exception
}
f( T(s) ); // conversion de type notation fonctionnelle
S a[3
] =
{
1
, 2
, 3
}
; // initialiseurs entre accolades
S*
p =
new
S(4
); // expression new
II. Solutions▲
II-A. Question Junior▲
Quelle est la différence entre « initialisation directe » et « initialisation par copie » ?
L'initialisation directe signifie que l'objet est initialisé avec un constructeur unique (éventuellement de conversion) et équivaut à la forme T t(u);(1) :
U u;
T t1(u); // appel de T::T( U& ) ou équivalent
L'initialisation par copie signifie que l'objet est initialisé avec un constructeur de copie, après avoir appelé pour la première fois une conversion définie par l'utilisateur si nécessaire. Elle équivaut à la forme T t = u;(2) :
T t2 =
t1; // mêmes types : appel de T::T( T& ) ou équivalent
T t3 =
u; // types différents : appel de T::T( T(u) )
// ou T::T( u.operator T() ) ou équivalent
En outre, la raison d'être des « ou équivalent » ci-dessus est que les constructeurs de copie et conversion pourraient prendre quelque chose de légèrement différent comme une référence (la référence pourrait être const ou volatile ou les deux) et le constructeur ou opérateur de conversion défini par l'utilisateur pourrait en plus prendre ou renvoyer un objet plutôt qu'une référence.
Note : dans le dernier cas T t3 = u;, le compilateur pourrait appeler à la fois la conversion définie par l'utilisateur (pour créer un objet temporaire) et le constructeur de copie T (pour construire t3 à partir du temporaire) ou il pourrait choisir d'éluder le temporaire et de construire t3 directement à partir de u (ce qui finirait pour équivaloir à T t3(u);"). Depuis juillet 1997 et dans le projet final de norme, la latitude du compilateur à éluder des objets temporaires a été restreinte, mais c'est encore permis pour cette optimisation et pour l'optimisation de la valeur retournée. Pour plus de détails, voir GotW #1 (pour les bases) et GotW #27 (pour les changements de 1997).
II-B. Question Guru▲
Lesquels des cas suivants utilisent l'initialisation directe et lesquels utilisent l'initialisation par copie ?
Le chapitre 8.5 [dcl.init] couvre la plupart de ces cas. Il y a aussi trois pièges qui en fait n'impliquent pas du tout l'initialisation... Les avez-vous repérés ?
struct
T : S {
T() : S(1
), // initialisation de base
x(2
) {}
// initialisation de membre
X x;
}
;
L'initialisation de base et l'initialisation de membre utilisent toutes les deux une initialisation directe.
T f( T t ) {
// passage d'un argument de fonction
return
t; // retour d'une valeur
}
Passer et retourner des valeurs utilisent tous les deux une initialisation par copie.
S s;
T t;
S&
r =
t;
reinterpret_cast
<
S>
(t); // effectuer un reinterpret_cast
Piège : reinterpret_cast n'initialise aucun objet : cela oblige seulement la réinterprétation de ses bits comme s'ils désignaient un S.
static_cast
<
S>
(t); // effectuer un static_cast
static_cast utilise une initialisation directe.
dynamic_cast
<
T&>
(r); // effectuer un dynamic_cast
const_cast
<
const
T&>
(t); // effectuer un const_cast
Autre piège : aucune initialisation d'objet nouveau n'est impliquée dans ces deux cas.
try
{
throw
T(); // lancer une exception
}
catch
( T t ) {
// traiter une exception
}
Lancer et attraper un objet d'exception utilisent l'un et l'autre une initialisation par copie.
Notez que dans ce code particulier, il y a deux copies pour un total de trois objets T : une copie de l'objet lancé est faite à l'emplacement du lancement et dans ce cas une seconde copie est faite parce que le catch attrape l'objet lancé par valeur.
f( T(s) ); // conversion de type notation fonctionnelle
La conversion de type « constructor syntax » utilise l'initialisation directe.
S a[3
] =
{
1
, 2
, 3
}
; // initialiseurs entre accolades
Les initialiseurs entre accolades utilisent l'initialisation par copie.
S*
p =
new
S(4
); // expression new
Les expressions new utilisent l'initialisation directe.
III. Remerciements▲
Cet article est une traduction en français par l'équipe de la rubrique C++ de l'article de Herb Sutter publié sur Guru of the Week. Vous pouvez retrouver cet article dans sa version originale sur le site de Guru of the Week : InitialisationInitialisation.
Merci à Luc Hermitte pour sa relecture technique et à ClaudeLELOUP pour sa relecture orthographique.