Pourquoi l’initialisation sans constructeur est nécessaire
(consteval.ca)Il faut initialiser même sans constructeur
-
Introduction
- En apprenant C++ pour la première fois, on découvre les cas où le compilateur fournit un constructeur par défaut.
- Cela amène à réfléchir au risque que, dans certaines situations, un objet ne soit pas initialisé.
-
Initialisation par défaut et initialisation par valeur
T t;effectue une initialisation par défaut.- Si
Test un type classe et possède un constructeur par défaut, celui-ci est exécuté. - Si
Test un type tableau, chaque élément est initialisé par défaut. - Sinon, rien n’est fait.
- Si
T t{};effectue une initialisation par valeur.- Si
Test un type classe, une initialisation par défaut est effectuée s’il n’a pas de constructeur par défaut, ou si celui-ci est fourni par l’utilisateur ou supprimé. - Sinon, il est d’abord initialisé à 0 puis initialisé par défaut.
- Si
Test un type tableau, chaque élément est initialisé par valeur. - Sinon, il est initialisé à 0.
- Si
-
Constructeur par défaut
- Si aucun constructeur par défaut n’est déclaré, le compilateur en déclare un implicitement.
- Le constructeur par défaut déclaré implicitement possède un corps vide et une liste d’initialisation des membres vide.
- Exemple :
struct T { int x; T() = default; }; T t{}; std::cout << t.x << std::endl; // le résultat affiché est 0
-
Constructeur par défaut défini implicitement
- Si un constructeur par défaut est déclaré implicitement ou explicitement avec
= default, le compilateur fournit un constructeur par défaut défini implicitement. - Exemple :
struct T { T(); }; T::T() = default; T t{}; std::cout << t.x << std::endl; // le résultat affiché est une valeur indéterminée
- Si un constructeur par défaut est déclaré implicitement ou explicitement avec
-
Cas où il est impossible de fournir un constructeur par défaut
- Quand
Tpossède un membre de référence non statique - Quand
Tpossède un membre non statique ou une classe de base non abstraite qui ne peut pas être construit par défaut ou détruit - Quand
Tpossède un membre non statiqueconstsans initialiseur de membre par défaut
- Quand
-
Initialisation correcte
T t{};effectue une initialisation par liste.- L’initialisation par liste se divise en initialisation directe par liste et initialisation par copie de liste.
- Exemple :
struct S { int a; float b; char c; }; S s{3, 4.0f, 'S'}; // aucun constructeur appelé
-
Initialisation par liste et initialisation agrégée
- L’initialisation agrégée est une forme particulière d’initialisation par liste, où chaque élément d’une classe ou d’un tableau est initialisé par copie à partir de chaque élément de la liste d’initialisation.
- Exemple :
struct A { const int x; }; A a{}; // a.x est initialisé à 0
-
Initialisation avec parenthèses
- L’initialisation avec parenthèses effectue une initialisation directe non par liste.
- Exemple :
struct T { const int& r; }; T t(42); // t.r est une référence vers 42
-
Résumé
- Les règles d’initialisation sont complexes, mais écrire soi-même un constructeur permet d’éviter la plupart des problèmes.
- Il vaut mieux ne pas s’en remettre au compilateur et écrire explicitement ses constructeurs.
L’avis de GN⁺
- Cet article explique bien la complexité des règles d’initialisation en C++.
- Comprendre les règles d’initialisation en C++ est important, car elles ont un impact majeur sur la robustesse et les performances du code.
- Écrire soi-même les constructeurs est la meilleure manière d’éviter les problèmes d’initialisation.
- Rust est un autre langage offrant des fonctionnalités similaires, avec des règles d’initialisation plus claires.
- Lorsqu’on adopte une nouvelle technologie, il est important de bien comprendre et maîtriser ce genre de détails, comme les règles d’initialisation.
1 commentaires
Commentaires sur Hacker News
Le résultat de l’initialisation de
tsera 0test initialisé par valeur et que, commeTn’a pas de constructeur par défaut défini par l’utilisateur, l’objet est d’abord initialisé à zéro puis le constructeur par défaut est appeléLe constructeur par défaut initialise les membres par défaut, ce qui est différent de l’initialisation par valeur
GCC semble être d’accord avec cela
L’auteur n’a pas vu qu’il initialisait en réalité
xpar valeurLes détails des règles sont complexes et parfois peu rationnels
Rendre l’initialisation par défaut explicite serait une grande amélioration
std::array<int, 100> = void;serait préférableLe lien entre l’initialisation par liste et l’initialisation d’agrégat est que lorsqu’une initialisation par liste est effectuée sur un agrégat, c’est une initialisation d’agrégat qui est réalisée
Le cas d’un seul élément fonctionne différemment de celui de deux éléments ou plus
On peut écrire son propre constructeur et initialiser un tuple ou un tableau avec un seul élément fourni
Quand les listes d’initialisation de C++11 sont apparues, certains l’ont remarqué et ont trouvé cela délirant
Mention de "I Have No Mouth, and I Must Scream" (1967)
Utilisation de la syntaxe
T::T() = default;On s’attend à ce que la sortie soit 0, mais en réalité ce sera une valeur indéterminée
Cela permet aux utilisateurs d’une bibliothèque de modifier le comportement de la bibliothèque
Pour encore plus de complexité C++, recommandation de lire la C++ FQA
Le thème du blog s’inspire des ordinateurs de l’époque DEC, tout en restant propre et minimaliste
Lire tout cela donne le vertige
Go et Rust n’ont pas de constructeurs spéciaux, ce qui simplifie beaucoup de choses
On se demande s’il existe un outil C++ qui montre tous les comportements implicites
Le texte donne des informations erronées sur la classe
L’affirmation selon laquelle
T t;« ne fait rien » est incorrecteT t;échoueL’en-tête du blog affiche un panneau frontal DEC