FAQ C++ moderne et erreurs courantes

Ce ou ces sujets font partie d'une FAQ en cours d'écriture, vos retours sont les bienvenus.
La liste d'initialisation est l'endroit où le constructeur crée tous ses membres ainsi que (éventuellement) la classe de base dont la classe hérite directement. Elle se situe avant l'accolade ouvrante qui contient le code du constructeur :
.hpp Sélectionnez
|
ou dans le .cpp : |
.cpp Sélectionnez
|
La liste d'initialisation débute par deux points :
et sépare chaque initialisation par une virgule ,. Chaque initialisation étant de la forme suivante (avec des parenthèses, ou des accolades depuis C++11 permettant de nouvelles formes d'initialisation) et construisant chaque membre dans le même ordre que leur déclaration :
ClasseDeBase(/* ses paramètres constructeurs */
)
variable_membre(/* ses paramètres constructeurs / sa valeur initiale */
)
Si l'on a par exemple une classe de base avec le constructeur ClasseDeBase(int
a, int
b) nous pourrons l'initialiser ainsi :
ClasseDeBase(1
, 2
)
Si l'on a par exemple un membre int
value_;, nous l'initialiserons ainsi :
value_(42
)
Si l'on a par exemple un membre std::
vector<
int
>
vec_; que l'on souhaite initialiser avec 10 fois la valeur 5, nous aurons :
vec_(10
, 5
)
ou avec les valeurs {1,2,3,4,5} :
vec_{
1
,2
,3
,4
,5
}
Le tout cumulé nous donnera :
Sélectionnez
|
ou dans le .cpp : |
Sélectionnez
|
La liste d'initialisation est le seul endroit où vous pouvez appeler les constructeurs de vos membres, en leur fournissant les bons paramètres. Ceci est d'autant plus vrai pour les éléments…
- non copiables/déplaçables : dans le corps du constructeur, l'instruction membre
=
sa_valeur; fera appel à l'operator
=
qui seradelete
; - qui n'ont pas de constructeurs par défaut, car non mentionnés dans cette liste, la default initialization sera appliquée s'ils n'ont pas été initialisés à la déclaration.
Comme vous pourrez le voir dans les démonstrations qui suivent, si vous tentez d'initialiser des éléments du 1., 2. ou 4. dans le corps {
… }
du constructeur, il est déjà trop tard et vous aurez une erreur de compilation.
-
les références : elles doivent obligatoirement désigner un objet dès leur construction, cf. '[…]' declared as reference but not initialized.
-
les classes de base et membres ne possédant pas de constructeur par défaut : vous êtes obligé de leur passer des paramètres :
-
les types primitifs, pointeurs nus et tableaux C : non initialisés, vous aurez un UB dès que vous voudrez lire leurs valeurs / accéder à l'adresse pointée :
-
les classes non copiables/déplaçables, en particulier les classes ayant une sémantique d'entité, (auquel s'ajoute éventuellement le 2.) : vous ne pourrez pas faire appel à l'opérateur d'affectation par copie et/ou déplacement dans le corps du constructeur :
Passons maintenant aux erreurs courantes (se basant sur l'exemple complet qui suit) :
Code erroné | Explications Correction |
---|---|
Sélectionnez
|
Non mentionnés dans la liste d'initialisation,
Retenez que :
Sélectionnez
|
Sélectionnez
ou idem avec un constructeur délégué : Sélectionnez
|
Vous remarquez déjà que vous avez commis l'erreur qui précède, il n'y a rien dans la liste d'initialisation. Que fait exactement cette ligne Base( Sélectionnez
En aucun cas, elle n'initialise quoique ce soit de la classe de base de l'instance qui est construite. Sélectionnez
Il en va de même avec le constructeur délégué. |
Sélectionnez
|
Au lieu d'être simplement initialisé à sa construction, str_ est construit par défaut dans la liste d'initialisation, puis modifié pour contenir la chaîne Cette manipulation reste faisable avec les types copiables, mais elle peut engendrer un coût inutile. Sélectionnez
|
Sélectionnez
|
Vous faites ici un appel comme si vos membres i_ et vec_ étaient des fonctions ou possédaient un membre Message d'erreur GCC Cacher/Afficher le codeSélectionnez Message d'erreur Clang Cacher/Afficher le codeSélectionnez Message d'erreur VS français Cacher/Afficher le codeSélectionnez Message d'erreur VS anglais Cacher/Afficher le codeSélectionnez Et ceci est extrêmement sujet à erreur si votre membre possède effectivement un tel opérateur : vous construisez votre membre par défaut et appelez cet opérateur, et ceci sans aucune erreur de compilation ! Sélectionnez
|
Sélectionnez
|
Ceci déclare une nouvelle variable locale indépendante qui porte le même nom que votre membre, mais n'a aucun lien avec. Sa modification n'entraînera pas celle de votre membre. Étant déclarée dans un scope plus proche que votre membre, c'est elle qui sera utilisée (et initialisée) à la place tout au long du constructeur, puis détruite lorsqu'on en sortira. Votre membre, lui n'aura pas été initialisé. (Cf. ce sujet[Erreur courante] Membres de classe vs variables locales.) Sélectionnez
|
Sélectionnez
|
Vous ne devez pas reporter le type Sélectionnez
|
Où puis-je utiliser la liste d'initialisation ?
Une liste d'initialisation peut seulement être utilisée dans les constructeurs de classe ou de structure, à savoir :
- le constructeur par défaut ;
- le constructeur par copie ;
- le constructeur par déplacement (move) ;
- n'importe quel autre constructeur que vous définirez.
Par contre, elle ne peut en aucun cas être utilisée ailleurs dans un opérateur ou une fonction membre classique, et encore moins dans une fonction libre.
L'ordre d'initialisation des membres est-il important ?
Quelque soit l'ordre dans lequel vous initialiserez vos membres et classe de base au sein de la liste d'initialisation, le compilateur initialisera d'abord la classe de base, puis les membres dans l'ordre dans lequel vous les avez déclarés. Dans la plupart des cas, cela n'aura pas d'importance, mais dans certains cas comme dans le suivant, cela pourra induire en erreur de les initialiser dans le désordre :
class
MaClasse {
int
premier; // déclaré en premier
int
second;
public
:
MaClasse(int
i) : second(i++
), premier(i++
) {
}
}
;
MaClasse mc(0
); // premier = 0 ; second = 1 et non l'inverse !
Si tel est le cas, votre compilateur (avec les bonnes options) vous préviendra avec le message :
warning: 'MaClasse::second' will be initialized after
[…]
warning: 'MaClasse::premier' [-Wreorder]
[…]
warning: when initialized here
[…]
Depuis C++11, vous pouvez :