1 points par GN⁺ 2024-07-06 | 1 commentaires | Partager sur WhatsApp

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 T est un type classe et possède un constructeur par défaut, celui-ci est exécuté.
      • Si T est un type tableau, chaque élément est initialisé par défaut.
      • Sinon, rien n’est fait.
    • T t{}; effectue une initialisation par valeur.
      • Si T est 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 T est un type tableau, chaque élément est initialisé par valeur.
      • Sinon, il est initialisé à 0.
  • 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
      
  • Cas où il est impossible de fournir un constructeur par défaut

    • Quand T possède un membre de référence non statique
    • Quand T possè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 T possède un membre non statique const sans initialiseur de membre par défaut
  • 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

 
GN⁺ 2024-07-06
Commentaires sur Hacker News
  • Le résultat de l’initialisation de t sera 0

    • Cela vient du fait que t est initialisé par valeur et que, comme T n’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é x par valeur

    • Le résultat est différent de ce qui était attendu
  • Les détails des règles sont complexes et parfois peu rationnels

    • Mais dans la plupart des cas, on peut obtenir le résultat attendu
  • Rendre l’initialisation par défaut explicite serait une grande amélioration

    • Comme l’initialisation par valeur est courante, il faut écrire un commentaire quand on veut une initialisation par défaut
    • Une syntaxe comme std::array<int, 100> = void; serait préférable
  • Le 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

    • Sauf si la liste ne contient qu’un seul argument, auquel cas une initialisation directe est effectuée
  • Le cas d’un seul élément fonctionne différemment de celui de deux éléments ou plus

    • Cela arrive dans un langage où créer une structure depuis un pack de paramètres devient de plus en plus simple
  • On peut écrire son propre constructeur et initialiser un tuple ou un tableau avec un seul élément fourni

    • Mais, dans des cas particuliers, le mauvais constructeur peut être appelé
  • 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

    • Certaines choses ne peuvent pas être parfaites
  • 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

    • Quinze ans plus tard, elle reste pertinente, car C++ supprime rarement ses anciennes fonctionnalités ou ses anciens comportements
  • Le thème du blog s’inspire des ordinateurs de l’époque DEC, tout en restant propre et minimaliste

    • C’est rafraîchissant
  • Lire tout cela donne le vertige

    • Cela rappelle le temps passé à essayer de comprendre les constructeurs Java et l’initialisation des objets
  • Go et Rust n’ont pas de constructeurs spéciaux, ce qui simplifie beaucoup de choses

    • On se demande si quelqu’un a déjà regretté les constructeurs après avoir cessé de les utiliser
  • On se demande s’il existe un outil C++ qui montre tous les comportements implicites

    • Par exemple tous les constructeurs ajoutés, les constructeurs de copie implicites, etc.
  • Le texte donne des informations erronées sur la classe

    • Si un constructeur est déclaré, alors aucun constructeur par défaut n’est fourni et l’initialisation par défaut échoue avec un diagnostic du compilateur
  • L’affirmation selon laquelle T t; « ne fait rien » est incorrecte

    • Dans l’exemple de code, T t; échoue
  • L’en-tête du blog affiche un panneau frontal DEC