Was ist diese seltsame Doppelpunkt-Member-Syntax (” : “) im Konstruktor?
Lesezeit: 9 Minuten
Nullen
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 das seltsam : bar(num) bedeuten? Es scheint irgendwie die Member-Variable zu initialisieren, aber ich habe diese Syntax noch nie zuvor gesehen. Es sieht aus wie ein Funktions-/Konstruktoraufruf, aber für eine int? Macht für mich keinen Sinn. Vielleicht könnte mich jemand aufklären. Und übrigens, gibt es noch andere esoterische Sprachfeatures wie diese, die Sie niemals in einem gewöhnlichen C++-Buch finden werden?
Ein “normales C++-Buch”, das dies nicht erwähnt, ist wahrscheinlich ein AC-Buch, bei dem jemand dachte, “++” würde auf dem Cover cool aussehen …
– Rasmus Kaj
10. November 2009 um 23:35 Uhr
“Sie werden es nie in einem gewöhnlichen C++-Buch finden”. Oh. Sehr geehrter. Werfen Sie Ihr „gewöhnliches C++-Buch“ sofort weg. Nicht aus dem Fenster – jemand anderes könnte es aufheben. Am besten zerkleinern und dem Recycling zuführen. Getan? Konsultieren Sie jetzt stackoverflow.com/questions/388242/…, um ein neues Buch zu erhalten.
– Steve Jessop
10. November 2009 um 23:36 Uhr
Dieses Sprachmerkmal ist kaum esoterisch. Es ist ein ziemlich wichtiges Merkmal der Objektkonstruktion.
– Karl Salvia
10. November 2009 um 23:36 Uhr
In der Tat, alles andere als esoterisch, haben Sie oft keine andere Wahl, als Initialisierungslisten zu verwenden. Wenn Ihre Klasse beispielsweise eine enthält const Mitgliedsvariable oder eine Referenz, müssen Sie eine Initialisierungsliste verwenden.
– Karl Salvia
10. November 2009 um 23:42 Uhr
Alok Speichern
Foo(int num): bar(num)
Dieses Konstrukt heißt a Mitgliedsinitialisiererliste in C++.
Einfach gesagt, es initialisiert Ihr Mitglied bar zu einem Wert num.
Was ist der Unterschied zwischen Initialisierung und Zuweisung innerhalb eines Konstruktors?
Member-Initialisierung:
Foo(int num): bar(num) {};
Mitgliedszuweisung:
Foo(int num)
{
bar = num;
}
Es besteht ein wesentlicher Unterschied zwischen dem Initialisieren eines Members mithilfe der Member-Initialisierungsliste und dem Zuweisen eines Werts innerhalb des Konstruktorkörpers.
Wenn du initialisieren Felder über die Member-Initialisierungsliste werden die Konstruktoren einmal aufgerufen und das Objekt wird in einer Operation konstruiert und initialisiert.
Wenn du benutzt Abtretung dann werden die Felder zuerst mit Standardkonstruktoren initialisiert und dann (über den Zuweisungsoperator) mit tatsächlichen Werten neu zugewiesen.
Wie Sie sehen, gibt es in letzterem einen zusätzlichen Aufwand für die 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 äquivalent zu:
Foo(int num) : bar() {bar = num;}
Während ersteres gleichbedeutend ist mit:
Foo(int num): bar(num){}
Für einen eingebauten (Ihr Codebeispiel) oder POD-Klassenmember gibt es keinen praktischen Overhead.
Wann MÜSSEN Sie die Member Initializer-Liste verwenden?
Du wirst haben (eher gezwungen). Verwenden Sie eine Member-Initialisierungsliste, wenn:
Ihre Klasse hat ein Referenzelement
Ihre Klasse hat ein nicht statisches Konstantenmitglied oder
Ihr Klassenmitglied hat keinen Standardkonstruktor oder
Zur Initialisierung von Basisklassenmitgliedern oder
Wenn der Parametername des Konstruktors mit dem Datenelement identisch ist (dies ist nicht wirklich ein 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, daher muss er über die Member-Initialisierungsliste initialisiert werden.
Basisklasse MyClass hat keinen Standardkonstruktor. Um sein Mitglied zu initialisieren, muss also die Member Initializer List verwendet werden.
Wichtige Punkte, die bei der Verwendung von Member Initializer Lists zu beachten sind:
Klassenmember-Variablen werden immer in der Reihenfolge initialisiert, in der sie in der Klasse deklariert werden.
Sie sind nicht in der Reihenfolge initialisiert, in der sie in der Member-Initialisierungsliste angegeben sind.
Kurz gesagt, die Member-Initialisierungsliste bestimmt nicht die Reihenfolge der Initialisierung.
In Anbetracht des Obigen ist es immer eine gute Praxis, die gleiche Reihenfolge der Member für die Member-Initialisierung beizubehalten wie die Reihenfolge, in der sie in der Klassendefinition deklariert sind. Dies liegt daran, dass Compiler nicht warnen, wenn die beiden Reihenfolgen unterschiedlich sind, aber ein relativ neuer Benutzer könnte die Member-Initialisiererliste mit der Reihenfolge der Initialisierung verwechseln und davon abhängigen Code schreiben.
@nils Das ist die bisher beste Antwort. Die Reihenfolge der Initialisierung, auf die Als hingewiesen wurde, ist ebenfalls äußerst wichtig, während der Visual Studio-Compiler nichts darüber sagt, andere Compiler wie gcc werden fehlschlagen. Es ist auch wichtig, je nach Compiler und Situation zu beachten nicht immer wahr dass dies die Leistung verbessert oder effizienter ist.
– ForceMagic
14. März 2012 um 7:21 Uhr
@ryf9059: Warum denkst du, dass es unpraktisch sein wird? Sie müssen sie sowieso auflisten, warum also nicht in der gleichen Reihenfolge wie bei der Deklaration?
– Alok Speichern
7. Januar 2013 um 12:10 Uhr
das hätte die Antwort sein sollen. Gott sei Dank habe ich nach unten gescrollt, sonst hätte ich es übersehen.
– Kaffeeliebhaber
13. August 2015 um 11:41 Uhr
@AlokSave MeineKlasse(int a, int b, int c) : i(a), b(b), k(c) { // Ohne Member-Initialisierer // this->b = b; } es sollte so sein MeineKlasse (int &a int b, int c) : i(a), b(b), k(c) { // Ohne Member-Initialisierer // this->b = b; } und damit entsprechende Änderungen in Deklaration und Aufruf. ohne diese Änderung i wird darauf verweisen a aber a kann nicht darauf verweisen x da es nur den Wert von enthält x also indirekt i kann nicht darauf verweisen x. Wenn wir also den Wert von ändern i dann ändere es einfach a aber nicht x
– Abhishek Mähne
15. Mai 2021 um 11:42 Uhr
@AbhishekMane Sie haben Recht, und hier ist ein Link zu der zugehörigen Frage, die dies zeigt: stackoverflow.com/q/67619383/3150802
– Peter – Setzen Sie Monica wieder ein
20. Mai 2021 um 12:08 Uhr
James McNellis
Es ist ein Member-Initialisierungsliste. Informationen darüber sollten Sie in jedem guten C++-Buch finden.
Wenn alle anderen Dinge gleich sind, wird Ihr Code schneller ausgeführt, wenn Sie Initialisierungslisten anstelle von Zuweisungen verwenden.
Die Kenntnis der Terminologie ist entscheidend – ich bin eifersüchtig, dass ich nicht daran gedacht habe.
– Markieren Sie Lösegeld
10. November 2009 um 23:36 Uhr
Es gibt auch eine Reihe anderer Gründe für die Verwendung von Init-Listen. besonders wenn die Reihenfolge der Initialisierung wichtig ist. Es ist eine Schande, dass es eine so dumme gefälschte Funktionsaufrufsyntax hat.
– Martin Beckett
10. November 2009 um 23:50 Uhr
@mgb, die Initialisierungsliste bestimmt nicht die Reihenfolge der Initialisierung. Member-Variablen werden in der Reihenfolge initialisiert, in der sie in der Klasse deklariert sind, auch wenn sich diese von der Reihenfolge der Initialisierungen im Konstruktor unterscheidet.
– ScottJ
11. November 2009 um 0:26 Uhr
@mgb: Ich glaube nicht, dass es als gefälschte Syntax für Funktionsaufrufe gedacht ist. Es ist Initialisierungssyntax, wie int i(23);, std::vector<double> emptyVec(0);, std::vector<double> fullVec(10,23.);usw. Natürlich nur mit entferntem Typ, da der Typ in der Elementdeklaration steht.
– Steve Jessop
11. November 2009 um 1:03 Uhr
@Martin: Es hat keine Funktionsaufrufsyntax, es hat eine Konstruktionssyntax (ala: new String (“Name”)). Es passt besser zum Konstruktor als Foo(int num) : m_Count = 5. Ganz zu schweigen davon, dass an dieser Stelle sowieso Klassen konstruiert werden müssen, da es hier initialisiert wird. Foo(int num) : Bar = num, würde nicht richtig kompilieren. Es scheint nur seltsam zu sein, Foo(int num) : m_Count(num) zu sehen, da primitive Typen nicht konstruiert werden.
– Lee Louviere
22. April 2011 um 20:33 Uhr
Josch
Das ist die Konstruktorinitialisierung. Es ist der richtige Weg, Member in einem Klassenkonstruktor zu initialisieren, da es verhindert, dass der Standardkonstruktor aufgerufen 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
Es geht um Effizienz.
Ich würde nicht sagen, dass es um Effizienz geht. Es geht darum, eine Möglichkeit bereitzustellen, etwas zu initialisieren, das eine Initialisierung erfordert, aber nicht standardmäßig initialisiert werden kann. Aus irgendeinem Grund erwähnen die Leute Konstanten und Referenzen als Beispiele, während das offensichtlichste Beispiel Klassen ohne Standardkonstruktoren wären.
– Ant
11. November 2009 um 0:26 Uhr
Wir haben beide recht; in seinem Beispiel könnte man für Effizienz argumentieren; Für das Problem const/reference/no default Konstruktor ist es sowohl Effizienz als auch Notwendigkeit. Ich habe deswegen eine Antwort unten hochgestuft 🙂 [Farnsworth voice] Es kann andere Dinge tun. Warum sollte es nicht?
– Josch
11. November 2009 um 4:19 Uhr
Bar bar(); // default constructor Bist du sicher?
– Leichtigkeitsrennen im Orbit
27. Dezember 2015 um 14:55 Uhr
@LightnessRacesinOrbit Nur neugierig zu wissen: Was soll das deiner Meinung nach sein?
– ajaysinghnegi
2. August 2019 um 11:01 Uhr
LeopardSkinPillBoxHat
Dies wird als Initialisierungsliste bezeichnet. Es ist eine Möglichkeit, Klassenmitglieder zu initialisieren. Es hat Vorteile, dies zu verwenden, anstatt den Mitgliedern im Hauptteil des Konstruktors einfach neue Werte zuzuweisen, aber wenn Sie Klassenmitglieder haben, die vorhanden sind Konstanten oder Verweise Sie Muss initialisiert werden.
Grundsätzlich gilt in Ihrem Fall x wird mit initialisiert _x, y mit _y, z mit _z.
Destruktor
Der andere hat Ihnen bereits erklärt, dass die von Ihnen beobachtete Syntax “Konstruktorinitialisiererliste” heißt. Mit dieser Syntax können Sie Basis-Unterobjekte und Mitglieds-Unterobjekte der Klasse benutzerdefiniert initialisieren (im Gegensatz dazu, dass sie standardmäßig initialisiert werden oder nicht initialisiert bleiben).
Ich möchte nur darauf hinweisen, dass die Syntax, die, wie Sie sagten, “wie ein Konstruktoraufruf aussieht”, nicht unbedingt ein Konstruktoraufruf ist. In der Sprache C++ ist die () Syntax ist nur eine Standardform von Initialisierungssyntax. Es wird für verschiedene Typen unterschiedlich interpretiert. Für Klassentypen mit benutzerdefiniertem Konstruktor bedeutet es eine Sache (es ist tatsächlich ein Konstruktoraufruf), für Klassentypen ohne benutzerdefinierten Konstruktor bedeutet es etwas anderes (sog Wertinitialisierung ) für leer ()) und für Nicht-Klassen-Typen bedeutet es wieder etwas anderes (da Nicht-Klassen-Typen keine Konstruktoren haben).
In Ihrem Fall hat das Datenelement Typ int. int ist kein Klassentyp, hat also keinen Konstruktor. Für Typ int Diese Syntax bedeutet einfach “initialisieren bar mit dem Wert von num” und das war’s. Es wird einfach so gemacht, direkt, keine Konstrukteure beteiligt, denn noch einmal, int ist kein Klassentyp und kann daher keine Konstruktoren haben.
Mark Lösegeld
Ich weiß nicht, wie man das übersehen könnte, es ist ziemlich einfach. Das ist die Syntax zum Initialisieren von Mitgliedsvariablen oder Basisklassenkonstruktoren. Es funktioniert sowohl für einfache alte Datentypen als auch für Klassenobjekte.
So in eine Zeile innerhalb der Deklaration geschrieben, ist es leicht, sie nicht als Init-Liste zu erkennen
– Martin Beckett
10. November 2009 um 23:51 Uhr
10035400cookie-checkWas ist diese seltsame Doppelpunkt-Member-Syntax (” : “) im Konstruktor?yes
Ein “normales C++-Buch”, das dies nicht erwähnt, ist wahrscheinlich ein AC-Buch, bei dem jemand dachte, “++” würde auf dem Cover cool aussehen …
– Rasmus Kaj
10. November 2009 um 23:35 Uhr
“Sie werden es nie in einem gewöhnlichen C++-Buch finden”. Oh. Sehr geehrter. Werfen Sie Ihr „gewöhnliches C++-Buch“ sofort weg. Nicht aus dem Fenster – jemand anderes könnte es aufheben. Am besten zerkleinern und dem Recycling zuführen. Getan? Konsultieren Sie jetzt stackoverflow.com/questions/388242/…, um ein neues Buch zu erhalten.
– Steve Jessop
10. November 2009 um 23:36 Uhr
Dieses Sprachmerkmal ist kaum esoterisch. Es ist ein ziemlich wichtiges Merkmal der Objektkonstruktion.
– Karl Salvia
10. November 2009 um 23:36 Uhr
In der Tat, alles andere als esoterisch, haben Sie oft keine andere Wahl, als Initialisierungslisten zu verwenden. Wenn Ihre Klasse beispielsweise eine enthält
const
Mitgliedsvariable oder eine Referenz, müssen Sie eine Initialisierungsliste verwenden.– Karl Salvia
10. November 2009 um 23:42 Uhr