wake-up-neo.com

Was ist diese seltsame Syntax für Doppelpunkte (":") im Konstruktor?

Kürzlich habe ich ein Beispiel wie das folgende gesehen:

#include <iostream>

class Foo {
public:
  int bar;
  Foo(int num): bar(num) {};
};

int main(void) {
  std::cout << Foo(42).bar << std::endl;
  return 0;
}

Was bedeutet dieses seltsame : bar(num)? Es scheint irgendwie, die Mitgliedsvariable zu initialisieren, aber ich habe diese Syntax noch nie gesehen. Es sieht aus wie ein Funktions-/Konstruktoraufruf, aber für ein int? Macht für mich keinen Sinn. Vielleicht könnte mich jemand aufklären. Übrigens, gibt es noch andere esoterische Sprachfunktionen wie diese, die Sie in keinem normalen C++ - Buch finden werden?

314
nils

Es ist eine Initialisierungsliste für Mitglieder . Informationen dazu finden Sie in jedem gutes C++ - Buch .

In den meisten Fällen sollten Sie alle Elementobjekte in der Elementinitialisierungsliste initialisieren. (Beachten Sie jedoch die Ausnahmen, die am Ende des FAQ Eintrags) aufgeführt sind.).

Der Abholpunkt vom FAQ Eintrag ist, dass,

Wenn alle anderen Faktoren gleich sind, wird Ihr Code schneller ausgeführt, wenn Sie Initialisierungslisten anstelle von Zuweisungen verwenden.

199
James McNellis
Foo(int num): bar(num)    

Dieses Konstrukt wird in C++ als Member Initializer List bezeichnet.

Einfach gesagt, es initialisiert Ihr Mitglied bar auf einen Wert num.


Was ist der Unterschied zwischen Initialisierung und Zuweisung in einem Konstruktor?

Member Initialization:

Foo(int num): bar(num) {};

Mitgliederzuordnung:

Foo(int num)
{
   bar = num;
}

Es gibt einen signifikanten Unterschied zwischen dem Initialisieren eines Elements mithilfe der Elementinitialisierungsliste und dem Zuweisen eines Werts innerhalb des Konstruktorkörpers.

Wenn Sie Felder über die Elementinitialisierungsliste initialisieren, werden die Konstruktoren einmal aufgerufen und das Objekt in einer Operation erstellt und initialisiert .

Wenn Sie die Zuweisung verwenden, werden die Felder zuerst mit Standardkonstruktoren initialisiert und dann (über den Zuweisungsoperator) mit tatsächlichen Werten neu zugewiesen .

Wie Sie sehen, entsteht in letzterem ein zusätzlicher Aufwand für Erstellung und Zuweisung, der für benutzerdefinierte Klassen erheblich sein kann.

Cost of Member Initialization = Object Construction 
Cost of Member Assignment = Object Construction + Assignment

Letzteres ist eigentlich gleichbedeutend mit:

Foo(int num) : bar() {bar = num;}

Während der erstere ist gleichbedeutend mit nur:

Foo(int num): bar(num){}

Für eingebaute (Ihr Codebeispiel) oder POD-Klassenmitglieder gibt es keinen praktischen Aufwand.


Wann MÜSSEN Sie die Mitgliederinitialisierungsliste verwenden?

Sie werden müssen (eher gezwungen) eine Member Initializer-Liste verwenden, wenn:

  • Ihre Klasse hat ein Referenzmitglied
  • Ihre Klasse hat ein nicht statisches const-Mitglied oder
  • Ihr Klassenmitglied hat keinen Standardkonstruktor oder
  • Zur Initialisierung von Basisklassenmitgliedern oder
  • Wenn der Parametername des Konstruktors mit dem Datenelement identisch ist (dies ist eigentlich kein MUSS)

Ein Codebeispiel:

class MyClass
{
    public:
        //Reference member, has to be Initialized in Member Initializer List
        int &i;       
        int b;
        //Non static const member, must be Initialized in Member Initializer List
        const int k;  

    //Constructor’s parameter name b is same as class data member 
    //Other way is to use this->b to refer to data member
    MyClass(int a, int b, int c):i(a),b(b),k(c)
    {
         //Without Member Initializer
         //this->b = b;
    }
};

class MyClass2:public MyClass
{
    public:
        int p;
        int q;
        MyClass2(int x,int y,int z,int l,int m):MyClass(x,y,z),p(l),q(m)
        {
        }

};

int main()
{
    int x = 10;
    int y = 20;
    int z = 30;
    MyClass obj(x,y,z);

    int l = 40;
    int m = 50;
    MyClass2 obj2(x,y,z,l,m);

    return 0;
}
  • MyClass2 hat keinen Standardkonstruktor und muss daher über die Member-Initialisierungsliste initialisiert werden.
  • Die Basisklasse MyClass hat keinen Standardkonstruktor. Um ihr Mitglied zu initialisieren, muss die Mitgliederinitialisierungsliste verwendet werden.

Wichtige Punkte, die bei der Verwendung von Elementinitialisierungslisten zu beachten sind:

Klassenmitgliedervariablen werden immer in der Reihenfolge initialisiert, in der sie in der Klasse deklariert sind.

Sie werden nicht in der Reihenfolge initialisiert, in der sie in der Mitgliederinitialisierungsliste angegeben sind.
Kurz gesagt, die Mitgliederinitialisierungsliste bestimmt nicht die Reihenfolge der Initialisierung.

In Anbetracht der obigen Ausführungen ist es immer ratsam, für die Initialisierung der Mitglieder die gleiche Reihenfolge beizubehalten, in der sie in der Klassendefinition deklariert sind. Dies liegt daran, dass Compiler nicht warnen, wenn sich die beiden Befehle unterscheiden, ein relativ neuer Benutzer jedoch die Initialisierungsliste des Mitglieds als Initialisierungsreihenfolge verwechseln und einen davon abhängigen Code schreiben kann.

301
Alok Save

Das ist die Konstruktorinitialisierung. Dies ist die richtige Methode zum Initialisieren von Mitgliedern in einem Klassenkonstruktor, da hierdurch das Aufrufen des Standardkonstruktors verhindert wird.

Betrachten Sie diese beiden Beispiele:

// Example 1
Foo(Bar b)
{
   bar = b;
}

// Example 2
Foo(Bar b)
   : bar(b)
{
}

In Beispiel 1:

Bar bar;  // default constructor
bar = b;  // assignment

In Beispiel 2:

Bar bar(b) // copy constructor

Alles dreht sich um Effizienz.

16
Josh

Dies wird als Initialisierungsliste bezeichnet. Dies ist eine Methode zum Initialisieren von Klassenmitgliedern. Es hat Vorteile, dies zu verwenden, anstatt den Elementen im Rumpf des Konstruktors einfach neue Werte zuzuweisen. Wenn Sie jedoch Klassenelemente haben, die Konstanten oder Referenzen sie - muss initialisiert werden.

14

Dies ist nicht unklar, es ist die C++ - Initialisierungslistensyntax

Grundsätzlich wird in Ihrem Fall x mit _x, y mit _y, z mit _z Initialisiert. .

9
wkl

Die andere hat Ihnen bereits erklärt, dass die Syntax, die Sie einhalten, "Konstruktorinitialisiererliste" heißt. Mit dieser Syntax können Sie Basis-Unterobjekte und Element-Unterobjekte der Klasse benutzerdefiniert initialisieren (anstatt zuzulassen, dass sie standardmäßig initialisiert werden oder nicht initialisiert werden).

Ich möchte nur bemerken, dass die Syntax, die, wie Sie sagten, "wie ein Konstruktoraufruf aussieht", nicht unbedingt ein Konstruktoraufruf ist. In C++ Sprache ist das () Syntax ist nur eine Standardform von Initialisierungssyntax. Es wird für verschiedene Typen unterschiedlich interpretiert. Für Klassentypen mit benutzerdefiniertem Konstruktor bedeutet dies eine Sache (es ist in der Tat ein Konstruktoraufruf), für Klassentypen ohne benutzerdefinierten Konstruktor bedeutet dies eine andere Sache (so genanntes = Wertinitialisierung ) für leeres ()) und für Nichtklassentypen bedeutet dies wiederum etwas anderes (da Nichtklassentypen keine Konstruktoren haben).

In Ihrem Fall hat das Datenelement den Typ int. int ist kein Klassentyp, hat also keinen Konstruktor. Für den Typ int bedeutet diese Syntax einfach "bar mit dem Wert num initialisieren" und das war's. Es wird einfach so gemacht, direkt, ohne dass Konstruktoren involviert sind, da int wiederum kein Klassentyp ist und daher keine Konstruktoren haben kann.

8
AnT

Ich weiß nicht, wie du das hier vermissen könntest, es ist ziemlich einfach. Dies ist die Syntax zum Initialisieren von Membervariablen oder Basisklassenkonstruktoren. Es funktioniert sowohl für einfache alte Datentypen als auch für Klassenobjekte.

7
Mark Ransom

Dies ist eine Initialisierungsliste. Die Member werden initialisiert, bevor der Konstruktortext ausgeführt wird. Erwägen

class Foo {
 public:
   string str;
   Foo(string &p)
   {
      str = p;
   };
 };

vs

class Foo {
public:
  string str;
  Foo(string &p): str(p) {};
};

Im ersten Beispiel wird str von seinem Konstruktor ohne Argumente initialisiert

string();

vor dem Körper des Foo-Konstruktors. Im foo Konstruktor wird der

string& operator=( const string& s );

wird bei 'str' aufgerufen, wie Sie es tun str = p;

Im zweiten Beispiel wird str direkt durch Aufrufen seines Konstruktors initialisiert

string( const string& s );

mit 'p' als Argument.

6
nos

Es ist eine Initialisierungsliste für den Konstruktor. Anstatt standardmäßig x, y und z zu konstruieren und ihnen dann die in den Parametern empfangenen Werte zuzuweisen, werden diese Elemente von Anfang an mit diesen Werten initialisiert. Dies mag für floats nicht sonderlich nützlich erscheinen, kann jedoch bei benutzerdefinierten Klassen, deren Konstruktion teuer ist, eine ziemliche Zeitersparnis bedeuten.

5
suszterpatt

Sie haben Recht, dies ist in der Tat eine Möglichkeit, Mitgliedsvariablen zu initialisieren. Ich bin mir nicht sicher, ob dies von großem Nutzen ist, abgesehen davon, dass es sich eindeutig um eine Initialisierung handelt. Ein "bar = num" im Code kann viel leichter verschoben, gelöscht oder falsch interpretiert werden.

5
Aric TenEyck

es gibt noch einen weiteren "Vorteil"

wenn der Elementvariablentyp keine Nullinitialisierung unterstützt oder eine Referenz ist (die nicht nullinitialisiert werden kann), müssen Sie eine Initialisierungsliste angeben

5
pm100

In diesem Thread noch nicht erwähnt: Seit C++ 11 kann die Liste der Member-Initialisierer die Listeninitialisierung verwenden (auch bekannt als "einheitliche Initialisierung", "geschweifte Initialisierung"):

Foo(int num): bar{num} {}

dies hat die gleiche Semantik wie die Listeninitialisierung in anderen Kontexten.

1
M.M