Gibt es in diesem speziellen Fall einen Unterschied zwischen der Verwendung einer Member-Initialisiererliste und der Zuweisung von Werten in einem Konstruktor?

90

Intern und in Bezug auf den generierten Code gibt es einen wirklichen Unterschied zwischen:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

und

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

Vielen Dank...

Stef
quelle

Antworten:

60

Angenommen, diese Werte sind primitive Typen, dann gibt es keinen Unterschied. Initialisierungslisten machen nur dann einen Unterschied, wenn Sie Objekte als Mitglieder haben, da Sie anstelle der Standardinitialisierung mit anschließender Zuweisung in der Initialisierungsliste das Objekt auf seinen endgültigen Wert initialisieren können. Dies kann tatsächlich spürbar schneller sein.

templatetypedef
quelle
17
Wie Richard sagte, macht es einen Unterschied, ob diese Werte primitive Typen und const sind. Initialisierungslisten sind die einzige Möglichkeit, const-Mitgliedern Werte zuzuweisen.
Thbusch
11
Es funktioniert nur wie beschrieben, wenn die Variablen keine Referenzen oder Konstanten sind. Wenn sie entweder konstant oder referenziert sind, wird es nicht einmal kompiliert, ohne die Initialisierungsliste zu verwenden.
stefanB
77

Sie müssen die Initialisierungsliste verwenden, um konstante Mitglieder, Referenzen und Basisklassen zu initialisieren

Wenn Sie konstante Elemente, Referenzen und Parameter an Basisklassenkonstruktoren initialisieren müssen, wie in den Kommentaren erwähnt, müssen Sie die Initialisierungsliste verwenden.

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

Beispiel (nicht erschöpfend) Klasse / Struktur enthält Referenz:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

Ein Beispiel für die Initialisierung einer Basisklasse, für die ein Parameter erforderlich ist (z. B. kein Standardkonstruktor):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};
stefanB
quelle
4
Und wenn _capacity, _dataund _lenhaben Klassentypen ohne zugängliche Standardkonstruktoren?
CB Bailey
2
Sie rufen auf, welche Konstruktoren verfügbar sind. Wenn Sie mehr Werte festlegen müssen, rufen Sie diese vom Hauptteil Ihres Konstruktors aus auf. Der Unterschied besteht darin, dass Sie das Element constim Konstruktorkörper nicht initialisieren können. Sie müssen die Initialisierungsliste verwenden. Nicht- constMitglieder können in der Initialisierungsliste oder im Konstruktorkörper initialisiert werden.
stefanB
@stefan: Konstruktoren kann man nicht aufrufen. Eine Klasse ohne Standardkonstruktor muss wie const-Mitglieder in der Initialisierungsliste initialisiert werden.
GManNickG
2
Sie müssen auch Referenzen in der Initialisierungsliste initialisieren
Andriy Tylychko
2
@stefanB: Ich entschuldige mich, wenn ich angedeutet habe, dass du das nicht verstehst, wenn du es tatsächlich tust. In Ihrer Antwort haben Sie nur angegeben, wann eine Basis oder ein Mitglied in der Initialisierungsliste benannt werden muss. Sie haben nicht erklärt, was der konzeptionelle Unterschied zwischen der Initialisierung in der Initialisierungsliste und der Zuweisung im Konstruktorkörper tatsächlich ist, wo mein Missverständnis liegt kann von gekommen sein.
CB Bailey
18

Ja. Im ersten Fall können Sie erklären _capacity, _dataund _lenals Konstanten:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Dies ist wichtig, wenn Sie die constAktualität dieser Instanzvariablen sicherstellen möchten, während Sie ihre Werte zur Laufzeit berechnen. Beispiel:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constInstanzen müssen in der Initialisierungsliste initialisiert werden, oder die zugrunde liegenden Typen müssen öffentliche parameterlose Konstruktoren bereitstellen (was primitive Typen tun).

Richard Cook
quelle
2
Gleiches gilt für Referenzen. Wenn Ihre Klasse Referenzmitglieder hat, müssen diese in der Initialisierungsliste initialisiert werden.
Mark Ransom
7

Ich denke, dieser Link http://www.cplusplus.com/forum/articles/17820/ bietet eine hervorragende Erklärung - insbesondere für diejenigen, die neu in C ++ sind.

Der Grund, warum Intialiser-Listen effizienter sind, besteht darin, dass innerhalb des Konstruktorkörpers nur Zuweisungen stattfinden, keine Initialisierung. Wenn Sie also mit einem nicht integrierten Typ arbeiten, wurde der Standardkonstruktor für dieses Objekt bereits aufgerufen, bevor der Hauptteil des Konstruktors eingegeben wurde. Innerhalb des Konstruktorkörpers weisen Sie diesem Objekt einen Wert zu.

Tatsächlich ist dies ein Aufruf des Standardkonstruktors, gefolgt von einem Aufruf des Kopierzuweisungsoperators. Mit der Initialisiererliste können Sie den Kopierkonstruktor direkt aufrufen. Dies kann manchmal erheblich schneller sein (denken Sie daran, dass sich die Initialisiererliste vor dem Hauptteil des Konstruktors befindet).

user929404
quelle
5

Ich füge hinzu, dass die Initialisierung die einzige Möglichkeit ist, Ihre Klasse zu erstellen, wenn Sie Mitglieder vom Klassentyp haben, für die kein Standardkonstruktor verfügbar ist.

Fred Larson
quelle
3

Ein großer Unterschied besteht darin, dass die Zuweisung Mitglieder einer übergeordneten Klasse initialisieren kann. Der Initialisierer funktioniert nur mit Mitgliedern, die im aktuellen Klassenbereich deklariert sind.

Mark Ransom
quelle
2

Hängt von den beteiligten Typen ab. Der Unterschied ist ähnlich zwischen

std::string a;
a = "hai";

und

std::string a("hai");

Wenn die zweite Form die Initialisierungsliste ist, macht es einen Unterschied, ob der Typ Konstruktorargumente erfordert oder mit Konstruktorargumenten effizienter ist.

Hündchen
quelle
1

Der wahre Unterschied besteht darin, wie der gcc-Compiler Maschinencode generiert und den Speicher festlegt. Erklären:

  • (Phase1) Vor dem Init-Body (einschließlich der Init-Liste): Der Compiler weist der Klasse den erforderlichen Speicher zu. Die Klasse lebt schon!
  • (Phase2) Im Init-Body: Da der Speicher zugewiesen ist, zeigt jede Zuweisung jetzt eine Operation für die bereits vorhandene / 'initialisierte' Variable an.

Es gibt sicherlich andere Möglichkeiten, mit Mitgliedern vom Typ const umzugehen. Um ihnen das Leben zu erleichtern, beschließen die Autoren des gcc-Compilers, einige Regeln aufzustellen

  1. Mitglieder vom Typ const müssen vor dem Init-Body initialisiert werden.
  2. Nach Phase1 gilt jede Schreiboperation nur für nicht konstante Mitglieder.
lukmac
quelle
1

Es gibt nur eine Möglichkeit, Basisklasseninstanzen und nicht statische Elementvariablen zu initialisieren , nämlich die Initialisierungsliste.

Wenn Sie in der Initialisierungsliste Ihres Konstruktors keine Basis- oder nicht statische Elementvariable angeben, wird dieses Element oder diese Basis entweder standardmäßig initialisiert (wenn das Element / die Basis ein Nicht-POD-Klassentyp oder ein Array einer Nicht-POD-Klasse ist Typen) oder anderweitig nicht initialisiert.

Sobald der Konstruktorkörper eingegeben wurde, wurden alle Basen oder Mitglieder initialisiert oder nicht initialisiert (dh sie haben einen unbestimmten Wert). Im Konstruktorkörper gibt es keine Möglichkeit zu beeinflussen, wie sie initialisiert werden sollen.

Möglicherweise können Sie Mitgliedern im Konstruktorkörper neue Werte zuweisen, aber es ist nicht möglich, constMitgliedern oder Mitgliedern des Klassentyps zuzuweisen, die nicht zuweisbar gemacht wurden, und es ist nicht möglich, Referenzen erneut zu binden.

Bei integrierten Typen und einigen benutzerdefinierten Typen kann die Zuweisung im Konstruktorkörper genau den gleichen Effekt haben wie die Initialisierung mit demselben Wert in der Initialisierungsliste.

Wenn Sie ein Mitglied oder eine Basis in einer Initialisierungsliste nicht benennen und diese Entität eine Referenz ist, einen Klassentyp ohne vom Benutzer deklarierten Standardkonstruktor hat, constqualifiziert ist und einen POD-Typ hat oder ein POD-Klassentyp oder ein Array vom POD-Klassentyp ist Wenn Sie ein constqualifiziertes Mitglied (direkt oder indirekt) enthalten, ist das Programm schlecht ausgebildet.

CB Bailey
quelle
0

Wenn Sie eine Initialisierungsliste schreiben, erledigen Sie alles in einem Schritt. Wenn Sie keine Initilizer-Liste schreiben, führen Sie zwei Schritte aus: einen für die Deklaration und einen für die Zuweisung des Werts.

L. Vicente Mangas
quelle
0

Es gibt einen Unterschied zwischen der Initialisierungsliste und der Initialisierungsanweisung in einem Konstruktor. Betrachten wir den folgenden Code:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

Wenn MyClass verwendet wird, werden alle Mitglieder vor der ersten Anweisung in einem ausgeführten Konstruktor initialisiert.

Wenn MyClass2 verwendet wird, werden jedoch nicht alle Mitglieder initialisiert, wenn die erste Anweisung in einem Konstruktor ausgeführt wird.

In einem späteren Fall kann es zu einem Regressionsproblem kommen, wenn jemand einem Konstruktor Code hinzufügt, bevor ein bestimmtes Mitglied initialisiert wird.

user928143
quelle
0

Hier ist ein Punkt, den andere nicht erwähnt haben:

class temp{
public:
   temp(int var);
};

Die temporäre Klasse hat keinen Standard-Ctor. Wenn wir es in einer anderen Klasse wie folgt verwenden:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

Der Code wird nicht kompiliert, da der Compiler nicht weiß, wie er initialisiert werden soll obj, weil er nur einen expliziten ctor hat, der einen int-Wert empfängt. Daher müssen wir den ctor wie folgt ändern:

mainClass(int sth):obj(sth){}

Es geht also nicht nur um const und Referenzen!

hosh0425
quelle