Guru Of the Week n° 36 : initialisation

Difficulté : 3 / 10
Quelle est la différence entre initialisation directe et initialisation par copie et quand doit-on les utiliser ?

Retrouvez l'ensemble des Guru of the Week sur la page d'index.

N'hésitez pas à commenter cet article ! 2 commentaires Donner une note à l'article (5)

Article lu   fois.

Les deux auteurs

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 ?

 
Sélectionnez
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) :

 
Sélectionnez
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) :

 
Sélectionnez
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 ?

 
Sélectionnez
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.

 
Sélectionnez
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électionnez
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.

 
Sélectionnez
static_cast<S>(t);        // effectuer un static_cast

static_cast utilise une initialisation directe.

 
Sélectionnez
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.

 
Sélectionnez
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.

 
Sélectionnez
f( T(s) );                // conversion de type notation fonctionnelle

La conversion de type « constructor syntax » utilise l'initialisation directe.

 
Sélectionnez
S a[3] = { 1, 2, 3 };     // initialiseurs entre accolades

Les initialiseurs entre accolades utilisent l'initialisation par copie.

 
Sélectionnez
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.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   


Note de traduction : le C++11 ajoute également la syntaxe T t { u }; pour l'initialisation directe.
Note de traduction : l'initialisation par copie peut également invoquer la sémantique de déplacement en C++11

  

Copyright © 2009 Herb Sutter. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.