1 Punkte von GN⁺ 2024-07-06 | 1 Kommentare | Auf WhatsApp teilen

Ich habe keinen Konstruktor und muss initialisiert werden

  • Einleitung

    • Als ich anfing, C++ zu lernen, habe ich gelernt, in welchen Fällen der Compiler einen Standardkonstruktor bereitstellt.
    • Dabei kam die Frage auf, welches Risiko besteht, dass ein Objekt in bestimmten Situationen nicht initialisiert wird.
  • Standardinitialisierung und Wertinitialisierung

    • T t; führt eine Standardinitialisierung aus.
      • Wenn T ein Klassentyp ist und einen Standardkonstruktor hat, wird dieser ausgeführt.
      • Wenn T ein Array-Typ ist, wird jedes Element standardinitialisiert.
      • Andernfalls passiert nichts.
    • T t{}; führt eine Wertinitialisierung aus.
      • Wenn T ein Klassentyp ist und keinen Standardkonstruktor hat oder einen benutzerdefinierten bzw. gelöschten Standardkonstruktor besitzt, wird standardinitialisiert.
      • Andernfalls wird zuerst mit 0 initialisiert und danach standardinitialisiert.
      • Wenn T ein Array-Typ ist, wird jedes Element wertinitialisiert.
      • Andernfalls wird mit 0 initialisiert.
  • Standardkonstruktor

    • Wenn kein Standardkonstruktor deklariert wird, deklariert der Compiler implizit einen Standardkonstruktor.
    • Ein implizit deklarierter Standardkonstruktor hat einen leeren Funktionsrumpf und eine leere Member-Initialisierungsliste.
    • Beispiel:
      struct T {
        int x;
        T() = default;
      };
      T t{};
      std::cout << t.x << std::endl; // Ausgabe ist 0
      
  • Implizit definierter Standardkonstruktor

    • Wenn ein Standardkonstruktor implizit deklariert oder explizit als Standard deklariert wird, stellt der Compiler einen implizit definierten Standardkonstruktor bereit.
    • Beispiel:
      struct T {
        T();
      };
      T::T() = default;
      T t{};
      std::cout << t.x << std::endl; // Ausgabe ist ein Garbage-Wert
      
  • Fälle, in denen kein Standardkonstruktor bereitgestellt werden kann

    • Wenn T ein nichtstatisches Referenz-Member besitzt
    • Wenn T ein nichtstatisches Member oder eine nichtabstrakte Basisklasse besitzt, die weder standardkonstruiert noch zerstört werden kann
    • Wenn T ein const-nichtstatisches Member ohne Default-Member-Initializer besitzt
  • Korrekte Initialisierung

    • T t{}; führt eine Listeninitialisierung aus.
    • Listeninitialisierung wird in direkte Listeninitialisierung und Copy-Listeninitialisierung unterteilt.
    • Beispiel:
      struct S {
        int a;
        float b;
        char c;
      };
      S s{3, 4.0f, 'S'}; // kein Konstruktoraufruf
      
  • Listeninitialisierung und Aggregatinitialisierung

    • Aggregatinitialisierung ist eine spezielle Form der Listeninitialisierung, bei der jedes Element einer Klasse oder eines Arrays per Copy-Initialisierung mit dem entsprechenden Element der Initialisierungsliste initialisiert wird.
    • Beispiel:
      struct A {
        const int x;
      };
      A a{}; // a.x wird mit 0 initialisiert
      
  • Initialisierung mit Klammern

    • Initialisierung mit Klammern führt eine direkte Nicht-Listeninitialisierung aus.
    • Beispiel:
      struct T {
        const int& r;
      };
      T t(42); // t.r ist eine Referenz auf 42
      
  • Zusammenfassung

    • Die Initialisierungsregeln sind komplex, aber durch das direkte Schreiben eines Konstruktors lassen sich die meisten Probleme vermeiden.
    • Statt sich auf den Compiler zu verlassen, ist es besser, den Konstruktor selbst zu schreiben.

Meinung von GN⁺

  • Dieser Artikel erklärt die Komplexität der C++-Initialisierungsregeln sehr gut.
  • Das Verständnis der Initialisierungsregeln in C++ ist wichtig, da es große Auswirkungen auf Stabilität und Performance des Codes hat.
  • Einen Konstruktor selbst zu schreiben, ist der beste Weg, Initialisierungsprobleme zu vermeiden.
  • Eine andere Sprache mit ähnlicher Funktionalität ist Rust; dort sind die Initialisierungsregeln klarer.
  • Bei der Einführung neuer Technologien ist es wichtig, auch Details wie Initialisierungsregeln genau zu verstehen und korrekt anzuwenden.

1 Kommentare

 
GN⁺ 2024-07-06
Hacker-News-Kommentar
  • Das Ergebnis der Initialisierung von t wird 0 sein

    • Das liegt daran, dass t wertinitialisiert wird und T keinen benutzerdefinierten Standardkonstruktor hat; daher wird das Objekt erst nullinitialisiert und danach der Standardkonstruktor aufgerufen
  • Der Standardkonstruktor standardinitialisiert die Member, was sich von der Wertinitialisierung unterscheidet

  • GCC scheint dem zuzustimmen

  • Der Autor hat übersehen, dass er tatsächlich x wertinitialisiert

    • Das Ergebnis fällt anders aus als erwartet
  • Die Details der Regeln sind komplex und teils unvernünftig

    • In den meisten Fällen erhält man aber das erwartete Ergebnis
  • Es wäre eine große Verbesserung, die Standardinitialisierung explizit ausdrücken zu können

    • Da Wertinitialisierung üblich ist, muss man einen Kommentar schreiben, wenn man Standardinitialisierung möchte
    • Eine Syntax wie std::array<int, 100> = void; wäre besser
  • Das Bindeglied zwischen Listeninitialisierung und Aggregatinitialisierung ist, dass bei einer Listeninitialisierung eines Aggregats eine Aggregatinitialisierung durchgeführt wird

    • Außer wenn die Liste nur ein einziges Argument enthält; dann wird Direktinitialisierung durchgeführt
  • Der Fall mit einem Element verhält sich anders als der mit zwei oder mehr Elementen

    • Das tritt in einer Sprache auf, in der das Erzeugen von Structs aus einem Parameter-Pack immer einfacher wird
  • Man kann einen eigenen Konstruktor schreiben und damit ein Tupel oder Array mit nur einem Element initialisieren

    • In Sonderfällen kann dann aber der falsche Konstruktor aufgerufen werden
  • Als die C++11-Initialisierungsliste neu war, habe ich das entdeckt und dachte, das sei völlig verrückt

  • Erwähnung von "I Have No Mouth, and I Must Scream" (1967)

  • Verwendung der Syntax T::T() = default;

  • Man erwartet, dass die Ausgabe 0 ist, tatsächlich wird aber ein Garbage-Wert ausgegeben

    • Manche Dinge lassen sich einfach nicht perfekt machen
  • Dadurch können Nutzer einer Bibliothek das Verhalten der Bibliothek ändern

  • Wer noch mehr C++-Komplexität will, dem sei die C++ FQA empfohlen

    • Obwohl 15 Jahre vergangen sind, ist sie immer noch relevant, weil C++ alte Features oder Verhaltensweisen kaum entfernt
  • Das Blog-Theme ist von Computern aus der DEC-Ära inspiriert, aber sauber und minimalistisch

    • Erfrischend
  • Beim Lesen davon wird einem schwindelig

    • Es weckt Erinnerungen daran, Java-Konstruktoren und Objektinitialisierung verstehen zu wollen
  • Go und Rust haben keine speziellen Konstruktoren, was vieles vereinfacht

    • Ich frage mich, ob jemand, nachdem er aufgehört hat, Konstruktoren zu verwenden, sie jemals vermisst hat
  • Ich frage mich, ob es ein C++-Tool gibt, das alle impliziten Verhaltensweisen sichtbar macht

    • Zum Beispiel alle hinzugefügten Konstruktoren, impliziten Copy-Konstruktoren usw.
  • Es werden falsche Informationen über die Klasse gegeben

    • Wenn man einen Konstruktor deklariert, wird kein Standardkonstruktor bereitgestellt, und Standardinitialisierung schlägt mit einer Compiler-Diagnose fehl
  • Die Behauptung, T t; tue "gar nichts", ist falsch

    • Im Beispielcode schlägt T t; fehl
  • Im Blog-Header ist ein DEC-Frontpanel zu sehen