it-swarm.com.de

C++ - Konstruktion eines Objekts innerhalb einer Klasse

Ich bin relativ neu in C++ und bin mir dessen nicht sicher. Schauen Sie sich das folgende Beispiel an, das mein aktuelles Problem zusammenfasst.

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

Bar enthält also ein vollständiges Foo-Objekt, nicht nur eine Referenz oder einen Zeiger. Wird dieses Objekt durch seinen Standardkonstruktor initialisiert? Muss ich seinen Konstruktor explizit aufrufen und wenn ja, wie und wo?

Vielen Dank.

22
SolarBear

Es wird von seinem Standardkonstruktor initialisiert. Wenn Sie einen anderen Konstruktor verwenden möchten, haben Sie möglicherweise Folgendes:

class Foo
{
    public: 
    Foo(int val) { }
    //stuff
};

class Bar
{
    public:
    Bar() : foo(2) { }

    Foo foo;
};
21

Konstruktion ist ein ziemlich hartes Thema in C++. Die einfache Antwort ist es kommt darauf an . Ob Foo initialisiert wird oder nicht, hängt von der Definition von Foo selbst ab. Über die zweite Frage: Wie kann man Baro Foo initialisieren: Initialisierungslisten sind die Antwort.

Während allgemeiner Konsens besteht, dass Foo standardmäßig durch den impliziten Standardkonstruktor (der Compiler wird generiert) initialisiert wird, muss dies nicht wahr sein. 

Wenn Foo keinen benutzerdefinierten Standardkonstruktor hat, wird Foo nicht initialisiert. Um genauer zu sein: Jedes Mitglied von Bar oder Foo ohne benutzerdefinierten Standardkonstruktor wird durch den vom Compiler generierten Standardkonstruktor von Bar nicht initialisiert :

class Foo {
   int x;
public:
   void dump() { std::cout << x << std::endl; }
   void set() { x = 5; }
};
class Bar {
   Foo x;
public:
   void dump() { x.dump(); }
   void set() { x.set(); } 
};
class Bar2
{
   Foo x;
public:
   Bar2() : Foo() {}
   void dump() { x.dump(); }
   void set() { x.set(); }
};
template <typename T>
void test_internal() {
   T x;
   x.dump();
   x.set();
   x.dump();
}
template <typename T>
void test() {
   test_internal<T>();
   test_internal<T>();
}
int main()
{
   test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
   test<Bar>(); // prints ??, 5, 5, 5
   test<Bar2>(); // prints 0, 5, 0, 5
}

Wenn Foo nun einen benutzerdefinierten Konstruktor hätte, würde dieser immer initialisiert, unabhängig davon, ob Bar einen vom Benutzer initialisierten Konstruktor hat oder nicht. Wenn Bar über einen benutzerdefinierten Konstruktor verfügt, der den (möglicherweise implizit definierten) Konstruktor von Foo explizit aufruft, wird Foo tatsächlich initialisiert. Wenn die Initialisierungsliste von Bar den Foo-Konstruktor nicht aufruft, entspricht dies dem Fall, in dem Bar keinen benutzerdefinierten Konstruktor hatte.

Der Testcode muss möglicherweise erklärt werden. Es interessiert uns, ob der Compiler die Variable initialisiert, ohne dass der Benutzercode den Konstruktor tatsächlich aufruft. Wir möchten testen, ob das Objekt initialisiert ist oder nicht. Wenn wir nun nur ein Objekt in einer Funktion erstellen, kann es passieren, dass eine unangemessene Speicherposition erreicht wird, die bereits Nullen enthält. Wir wollen das Glück vom Erfolg unterscheiden, also definieren wir eine Variable in einer Funktion und rufen die Funktion zweimal auf. Im ersten Lauf wird der Speicherinhalt gedruckt und eine Änderung erzwungen. Beim zweiten Aufruf der Funktion wird die Variable an genau derselben Speicherposition gehalten, da der Stack-Trace derselbe ist. Wenn es initialisiert wurde, wird es auf 0 gesetzt, andernfalls wird derselbe Wert beibehalten, den die alte Variable an genau derselben Position hatte.

Bei jedem Testdurchlauf ist der erste gedruckte Wert der initialisierte Wert (wenn er tatsächlich initialisiert wurde) oder der Wert an dieser Speicherposition, der in einigen Fällen 0 ist. Der zweite Wert ist nur ein Test-Token, das den Wert darstellt Nach dem manuellen Ändern an der Speicherposition. Der dritte Wert stammt aus dem zweiten Durchlauf der Funktion. Wenn die Variable initialisiert wird, fällt sie auf 0 zurück. Wenn das Objekt nicht initialisiert wird, behält der Speicher den alten Inhalt bei.

Es gibt vier Funktionen, die der C++ - Compiler für jede Klasse generiert, sofern dies möglich ist und nicht angegeben wird: einen Standardkonstruktor, einen Kopienkonstruktor, einen Zuweisungsoperator und einen Destruktor. Im C++ - Standard (Kapitel 12, "Sonderfunktionen") werden diese als "implizit deklariert" und "implizit definiert" bezeichnet. Sie werden öffentlich zugänglich sein.

Verwechseln Sie "implizit definiert" nicht mit "default" in einem Konstruktor. Der Standardkonstruktor ist derjenige, der ohne Argumente aufgerufen werden kann, falls vorhanden. Wenn Sie keinen Konstruktor angeben, wird implizit ein Standard-Konstruktor definiert. Es verwendet die Standardkonstruktoren für jede Basisklasse und jedes Datenmitglied.

Was passiert ist also, dass die Klasse Foo einen implizit definierten Standardkonstruktor hat, und Bar (der scheinbar keinen benutzerdefinierten Konstruktor hat) verwendet seinen implizit definierten Standardkonstruktor, der den Standardkonstruktor von Foo aufruft.

Wenn Sie einen Konstruktor für Bar schreiben möchten, können Sie foo in der Initialisierungsliste erwähnen. Da Sie jedoch den Standardkonstruktor verwenden, müssen Sie ihn nicht wirklich angeben.

Denken Sie daran, dass der Compiler beim Erstellen eines Konstruktors für Foo nicht automatisch einen Standardkonstruktor generiert. Wenn Sie einen Konstruktor benötigen, müssen Sie ihn angeben. Wenn Sie also etwas wie Foo(int n); in die Definition von Foo einbauen und keinen Standardkonstruktor (entweder Foo(); oder Foo(int n = 0);) explizit schreiben, können Sie keine Leiste in ihrer aktuellen Form verwenden, da sie nicht verwendet werden kann Der Standardkonstruktor von Foo. In diesem Fall müssten Sie über einen Konstruktor wie Bar(int n = 0): foo(n) {} verfügen, bei dem der Bar-Konstruktor das Foo initialisiert. (Beachten Sie, dass Bar(int n = 0) {foo = n;} oder dergleichen nicht funktionieren würde, da der Bar-Konstruktor zuerst versuchen würde, foo zu initialisieren, und dies würde fehlschlagen.)

5
David Thornley

Wenn Sie nicht explizit einen Konstruktor von foo innerhalb des Bar-Konstruktors aufrufen, wird der Standardkonstruktor verwendet. Sie können dies steuern, indem Sie den Konstruktor explizit aufrufen

Bar::Bar() : foo(42) {}

Dies setzt natürlich voraus, dass Sie dem Code ein Foo :: Foo (int) hinzufügen :)

2
JaredPar

Konstruktor zum Festlegen eines Anfangszustands eines Objekts. Im Falle einer Vererbungshierarchie werden Basisklassenobjekte in der Reihenfolge der Vererbungshierarchie (IS-A-Beziehung in der Terminologie OO) erstellt, gefolgt von abgeleiteten Klassenobjekten.

Wenn ein Objekt in ein anderes Objekt eingebettet ist (HAS-A-Relation in OO-Begriffen oder Containment), werden Konstruktoren von eingebetteten Objekten in der Reihenfolge ihrer Deklaration aufgerufen.

Der Compiler analysiert alle Member der Klasse B und generiert dann den Code für jede Methode. Zu diesem Zeitpunkt kennt es alle Member und deren Reihenfolge und erstellt die Member in der angegebenen Reihenfolge. Die Standardkonstruktoren werden verwendet, sofern nichts anderes angegeben ist. Foo wird also konstruiert, ohne explizit seinen Standardkonstruktor aufzurufen. Sie müssen lediglich ein Objekt von Bar erstellen.

1
user6882413

Bar enthält also ein vollständiges Foo-Objekt, nicht nur eine Referenz oder einen Zeiger. Wird dieses Objekt durch seinen Standardkonstruktor initialisiert?

Wenn Foo einen Standardwert hat, verwendet ein Objekt vom Typ Foo den Standardwert, wenn Sie ein Objekt vom Typ Bar erstellen. Andernfalls müssen Sie den Foo-ctor selbst anrufen, oder Ihr Bar-ctor wird Ihren Compiler zur Beschwerde einladen.

Z.B:

class Foo {
public:
 Foo(double x) {}
};

class Bar  {
 Foo x;
};

int main() {
 Bar b;
}

Das obige wird den Compiler so etwas beschweren:

"Im Konstruktor 'Bar :: Bar ()': Zeile 5: Fehler: Keine passende Funktion für Aufruf von 'Foo :: Foo ()'

Muss ich seinen Konstruktor explizit aufrufen und wenn ja, wie und wo?

Ändern Sie das obige Beispiel wie folgt:

class Foo {
 public:
  Foo(double x) {} // non-trivial ctor
};

class Bar  {     
 Foo x;
public:
  Bar() : x(42.0) {} // non-default ctor, so public access specifier required
};

int main() {
 Bar b;
}
1
dirkgently

Volles Objekt Nein, es ist standardmäßig im Standardkonstruktor von Bar erstellt.

Wenn Foo nun einen Konstruktor hätte, der nur ein Int. Sie benötigen einen Konstruktor in Bar, um den Konstruktor von Foo aufzurufen und zu sagen, was das ist:

class Foo {
public:
    Foo(int x) { .... }
};

class Bar {
public:
    Bar() : foo(42) {}

    Foo foo;
};

Wenn Foo über einen Standardkonstruktor Foo () verfügt, generiert der Compiler automatisch den Bar-Konstruktor. Dies würde den Standardwert von Foo (d. H. Foo ()) aufrufen.

1
Macke

Wenn Sie nichts anderes angeben, wird foo mit seinem Standardkonstruktor initialisiert. Wenn Sie einen anderen Konstruktor verwenden möchten, müssen Sie dies in der Initialisierungsliste für Bar tun:

Bar::Bar( int baz ) : foo( baz )
{
    // Rest of the code for Bar::Bar( int ) goes here...
}
1
Naaff

Sie müssen den Standardkonstruktor nicht explizit in C++ aufrufen. Er wird für Sie aufgerufen. Wenn Sie einen anderen Konstruktor aufrufen möchten, können Sie Folgendes tun:

Foo foo(somearg)
0
Shane C. Mason