Warum sollte ich die Mitgliederinitialisierungsliste bevorzugen?

Antworten:

277

Für POD- Klassenmitglieder macht es keinen Unterschied, es ist nur eine Frage des Stils. Bei Klassenmitgliedern, bei denen es sich um Klassen handelt, wird ein unnötiger Aufruf eines Standardkonstruktors vermieden. Erwägen:

class A
{
public:
    A() { x = 0; }
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B()
    {
        a.x = 3;
    }
private:
    A a;
};

In diesem Fall Bruft der Konstruktor für den Standardkonstruktor für auf Aund initialisiert ihn dann a.xauf 3. Eine bessere Möglichkeit wäre, wenn Bder Konstruktor den Konstruktor direkt Ain der Initialisierungsliste aufruft:

B()
  : a(3)
{
}

Dies würde nur Aden A(int)Konstruktor und nicht den Standardkonstruktor aufrufen . In diesem Beispiel ist der Unterschied vernachlässigbar, aber stellen Sie sich vor, Ader Standardkonstruktor hat mehr getan, z. B. Speicher zuweisen oder Dateien öffnen. Sie würden das nicht unnötig tun wollen.

Wenn eine Klasse keinen Standardkonstruktor oder eine constMitgliedsvariable hat, müssen Sie außerdem eine Initialisierungsliste verwenden:

class A
{
public:
    A(int x_) { x = x_; }
    int x;
};

class B
{
public:
    B() : a(3), y(2)  // 'a' and 'y' MUST be initialized in an initializer list;
    {                 // it is an error not to do so
    }
private:
    A a;
    const int y;
};
Adam Rosenfield
quelle
5
Ein Muss ist auch für einen wichtigen Fall einer Referenz
4pie0
5
Warum nicht "a (3)" verwenden? oder "a = A (3);" im Hauptteil des Standardkonstruktors von B?
Sergey
1
Können Sie erklären, was Sie mit POD meinen?
Jonas Stein
2
@ JonasStein POD ist ein genau definierter Satz von Regeln, die sich auf einfache Datenstrukturen beziehen (und nicht auf vollständige Klassen). Lesen Sie die FAQ für mehr: stackoverflow.com/questions/146452/what-are-pod-types-in-c
monkey0506
2
@Sergey, der Standardkonstruktor des A wird weiterhin aufgerufen.
Vassilis
44

Abgesehen von den oben genannten Leistungsgründen haben Sie keine andere Wahl, als Initialisierungslisten zu verwenden, wenn Ihre Klasse Verweise auf Objekte speichert, die als Konstruktorparameter übergeben wurden, oder wenn Ihre Klasse const-Variablen enthält.

Naveen
quelle
7
Gleiches gilt für Const-Mitglieder, glaube ich.
Richard Corden
Ja, die Zuweisung kann nicht zum Ändern der const-Variablen verwendet werden, daher muss sie initialisiert werden.
Hareen Laks
23
  1. Initialisierung der Basisklasse

Ein wichtiger Grund für die Verwendung der Konstruktorinitialisiererliste, die hier in den Antworten nicht erwähnt wird, ist die Initialisierung der Basisklasse.

Gemäß der Reihenfolge der Konstruktion sollte die Basisklasse vor der untergeordneten Klasse erstellt werden. Ohne Liste der Konstruktorinitialisierer ist dies möglich, wenn Ihre Basisklasse über einen Standardkonstruktor verfügt, der unmittelbar vor der Eingabe des Konstruktors der untergeordneten Klasse aufgerufen wird.

Wenn Ihre Basisklasse jedoch nur einen parametrisierten Konstruktor hat, müssen Sie die Konstruktorinitialisierungsliste verwenden, um sicherzustellen, dass Ihre Basisklasse vor der untergeordneten Klasse initialisiert wird.

  1. Initialisierung von Unterobjekten, die nur parametrisierte Konstruktoren haben

  2. Effizienz

Mithilfe der Konstruktorinitialisiererliste initialisieren Sie Ihre Datenelemente auf den genauen Status, den Sie in Ihrem Code benötigen, anstatt sie zuerst auf ihren Standardstatus zu initialisieren und dann ihren Status auf den Status zu ändern, den Sie in Ihrem Code benötigen.

  1. Initialisieren nicht statischer const-Datenelemente

Wenn nicht statische const-Datenelemente in Ihrer Klasse Standardkonstruktoren haben und Sie keine Konstruktorinitialisierungsliste verwenden, können Sie sie nicht in den beabsichtigten Zustand initialisieren, da sie in ihren Standardzustand initialisiert werden.

  1. Initialisierung von Referenzdatenelementen

Referenzdatenelemente müssen initialisiert werden, wenn der Compiler den Konstruktor betritt, da Referenzen nicht einfach deklariert und später initialisiert werden können. Dies ist nur mit der Konstruktorinitialisiererliste möglich.

Yuvi
quelle
10

Neben den Leistungsproblemen gibt es noch einen sehr wichtigen, den ich als Wartbarkeit und Erweiterbarkeit des Codes bezeichnen würde.

Wenn ein T POD ist und Sie anfangen, die Initialisierungsliste zu bevorzugen, müssen Sie, wenn T einmal in einen Nicht-POD-Typ wechselt, nichts an der Initialisierung ändern, um unnötige Konstruktoraufrufe zu vermeiden, da diese bereits optimiert ist.

Wenn Typ T über einen Standardkonstruktor und einen oder mehrere benutzerdefinierte Konstruktoren verfügt und Sie sich einmal dazu entschließen, den Standardkonstruktor zu entfernen oder auszublenden, müssen Sie bei Verwendung der Initialisierungsliste den Code nicht aktualisieren, wenn Ihre benutzerdefinierten Konstruktoren vorhanden sind Sie sind bereits korrekt implementiert.

Nehmen wir an, T ist zunächst wie folgt definiert:

struct T
{
    T() { a = 5; }
private:
    int a;
};

Als nächstes entscheiden Sie sich, eine als const zu qualifizieren. Wenn Sie die Initialisierungsliste von Anfang an verwenden würden, wäre dies eine Änderung in einer einzelnen Zeile. Wenn Sie jedoch das T wie oben definiert haben, müssen Sie auch die Konstruktordefinition ausgraben, um die Zuordnung zu entfernen:

struct T
{
    T() : a(5) {} // 2. that requires changes here too
private:
    const int a; // 1. one line change
};

Es ist kein Geheimnis, dass die Wartung viel einfacher und weniger fehleranfällig ist, wenn Code nicht von einem "Code-Affen" geschrieben wurde, sondern von einem Ingenieur, der Entscheidungen trifft, die auf tieferen Überlegungen darüber beruhen, was er tut.

Mloskot
quelle
5

Bevor der Hauptteil des Konstruktors ausgeführt wird, werden alle Konstruktoren für seine übergeordnete Klasse und dann für seine Felder aufgerufen. Standardmäßig werden die Konstruktoren ohne Argumente aufgerufen. In Initialisierungslisten können Sie auswählen, welcher Konstruktor aufgerufen wird und welche Argumente der Konstruktor erhält.

Wenn Sie eine Referenz oder ein const-Feld haben oder wenn eine der verwendeten Klassen keinen Standardkonstruktor hat, müssen Sie eine Initialisierungsliste verwenden.

Jamal Zafar
quelle
2
// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate 
                     // constructors and operators
        variable = a;
    }
};

Hier führt der Compiler die folgenden Schritte aus, um ein Objekt vom Typ MyClass
1 zu erstellen. Der Konstruktor des Typs wird zuerst für "a" aufgerufen.
2. Der Zuweisungsoperator von "Type" wird innerhalb des zuweisenden MyClass () -Konstruktors aufgerufen

variable = a;
  1. Und schließlich wird der Destruktor von "Type" für "a" aufgerufen, da er außerhalb des Gültigkeitsbereichs liegt.

    Betrachten Sie nun denselben Code mit dem MyClass () -Konstruktor mit der Initializer-Liste

    // With Initializer List
     class MyClass {
    Type variable;
    public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
    };

    Bei der Initialisierungsliste werden vom Compiler die folgenden Schritte ausgeführt:

    1. Der Kopierkonstruktor der Klasse "Type" wird aufgerufen, um Folgendes zu initialisieren: Variable (a). Die Argumente in der Initialisierungsliste werden verwendet, um das Konstrukt "Variable" direkt zu kopieren.
    2. Der Destruktor vom Typ "Typ" wird als "a" bezeichnet, da er außerhalb des Gültigkeitsbereichs liegt.
Rahul Singh
quelle
2
Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung aus dem Code wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen. Bitte versuchen Sie auch, Ihren Code nicht mit erklärenden Kommentaren zu überfüllen. Dies verringert die Lesbarkeit sowohl des Codes als auch der Erklärungen! meta.stackexchange.com/q/114762/308249
Davejal
2
Bitte schreiben Sie Ihr eigenes Verständnis oder teilen Sie einfach den Link zur Originalquelle (hier geeksforgeeks.com), anstatt ihn nur zu kopieren.
Yuvi
1

Nur um einige zusätzliche Informationen hinzuzufügen, um zu demonstrieren, wie viel Unterschied die Mitgliederinitialisierungsliste machen kann . In der Leetcode 303 Range Sum Query - Unveränderlich, https://leetcode.com/problems/range-sum-query-immutable/ , wo Sie einen Vektor mit einer bestimmten Größe erstellen und auf Null initialisieren müssen. Hier sind zwei verschiedene Implementierungs- und Geschwindigkeitsvergleiche.

Ohne Mitgliederinitialisierungsliste kostete es mich ungefähr 212 ms , um AC zu bekommen .

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
    preSum = vector<int>(nums.size()+1, 0);
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};

Bei Verwendung der Mitgliederinitialisierungsliste beträgt die Zeit zum Abrufen des Wechselstroms ungefähr 108 ms . Mit diesem einfachen Beispiel ist es ziemlich offensichtlich, dass die Mitgliederinitialisierungsliste viel effizienter ist . Die gesamte Messung erfolgt aus der Laufzeit von LC.

class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) { 
    int ps = 0;
    for (int i = 0; i < nums.size(); i++)
    {
        ps += nums[i];
        preSum[i+1] = ps;
    }
}

int sumRange(int i, int j) {
    return preSum[j+1] - preSum[i];
}
};
Yi Wang
quelle
0

Syntax:

  class Sample
  {
     public:
         int Sam_x;
         int Sam_y;

     Sample(): Sam_x(1), Sam_y(2)     /* Classname: Initialization List */
     {
           // Constructor body
     }
  };

Liste der erforderlichen Initialisierungen:

 class Sample
 {
     public:
         int Sam_x;
         int Sam_y;

     Sample()     */* Object and variables are created - i.e.:declaration of variables */*
     { // Constructor body starts 

         Sam_x = 1;      */* Defining a value to the variable */* 
         Sam_y = 2;

     } // Constructor body ends
  };

Wenn im obigen Programm der Konstruktor der Klasse ausgeführt wird, werden Sam_x und Sam_y erstellt. Dann werden im Konstruktorkörper diese Elementdatenvariablen definiert.

Anwendungsfälle:

  1. Konstanten- und Referenzvariablen in einer Klasse

In C, Variablen müssen bei der Erstellung definiert werden. Auf die gleiche Weise müssen wir in C ++ die Variablen Const und Reference während der Objekterstellung mithilfe der Initialisierungsliste initialisieren. Wenn wir nach der Objekterstellung eine Initialisierung durchführen (innerhalb des Konstruktorkörpers), wird ein Fehler bei der Kompilierung angezeigt.

  1. Mitgliedsobjekte der Klasse Sample1 (Basis), die keinen Standardkonstruktor haben

     class Sample1 
     {
         int i;
         public:
         Sample1 (int temp)
         {
            i = temp;
         }
     };
    
      // Class Sample2 contains object of Sample1 
     class Sample2
     {
      Sample1  a;
      public:
      Sample2 (int x): a(x)      /* Initializer list must be used */
      {
    
      }
     };

Beim Erstellen eines Objekts für eine abgeleitete Klasse, das intern den Konstruktor für abgeleitete Klassen und den Konstruktor für Basisklassen aufruft (Standard). Wenn die Basisklasse keinen Standardkonstruktor hat, wird dem Benutzer ein Fehler bei der Kompilierung angezeigt. Um dies zu vermeiden, müssen wir beides haben

 1. Default constructor of Sample1 class
 2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
  1. Der Parametername des Klassenkonstruktors und das Datenelement einer Klasse sind identisch:

     class Sample3 {
        int i;         /* Member variable name : i */  
        public:
        Sample3 (int i)    /* Local variable name : i */ 
        {
            i = i;
            print(i);   /* Local variable: Prints the correct value which we passed in constructor */
        }
        int getI() const 
        { 
             print(i);    /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
             return i; 
        }
     };

Wie wir alle wissen, hat die lokale Variable die höchste Priorität als die globale Variable, wenn beide Variablen denselben Namen haben. In diesem Fall berücksichtigt das Programm den Wert "i" {sowohl die linke als auch die rechte Variable. dh: i = i} als lokale Variable im Sample3 () -Konstruktor und Klassenklassenvariable (i) wurde überschrieben. Um dies zu vermeiden, müssen wir entweder verwenden

  1. Initialization list 
  2. this operator.
Eswaran Pandi
quelle