Peter_2498 schrieb:
1. Wieso brauchen wir überhaupt verschiedene Konstruktoren?
Peter_2498 schrieb:
2. Wieso braucht der Compiler einen Default-Konstruktor, wenn er schon einen vom Benutzer definierten Konstruktor hat ?
Kurz: Weil es verschiedene Arten gibt, auf die man ein Objekt erzeugen kann. Diese Konstruktoren sind sog. "Special Member Functions".
Ein kleines, motivierendes Beispiel:
Code:
#pragma once
#include <string>
class Person
{
public:
Person() = default; // default C'tor, ist erstmal nur drin, damit das Beispiel kompiliert
Person(std::string name, int age) : m_name(std::move(name)), m_age(age) {} // benuterdefinierter C'tor
private:
std::string m_name{""};
int m_age{0};
};
int main()
{
// Welcher Konstruktor wird jeweils benutzt?
Person p0;
Person p1("Bob", 37);
Person p2(p1);
Person p3 = p1;
Person p4(Person("Alice", 33));
Person p5 = std::move(p4);
}
Es ist intuitiv zu denken: Ich habe einen Konstruktor (Zeile 9) angegeben, also ist nun definiert, wie ein Objekt vom Typ
Person
zu erzeugen ist. Leider ist es, wie immer, nicht so einfach.
Dir wird bereits selbst auffallen, dass sich die Zeilen 19 bis 24 nur einmal an den von dir vorgegebenen Bauplan halten. Dennoch sind alle anderen Arten völlig valide und funktionieren.
Der Grund sind die "Special Member Functions", die der Compiler für dich implizit erzeugt hat. Bis auf den Default Konstruktor, den ich deshalb selbst geschrieben habe. (
Siehe hier) Um Missverständnissen vorzubeugen: Das
= default
bedeutet nicht, dass es der Default Konstruktor ist, sondern dass eine Default Implementierung angegeben werden soll.
Die Special Member functions sind: (
Siehe hier ganz unten)
- Default Constructor
- Copy Constructor
- Copy Assignment Operator
- Move Constructor
- Move assignment Operator
- Destructor
Darüberhinaus kannst du weitere, benutzerdefinierte Konstruktoren angeben.
Nun zu deiner Frage, warum der Compiler den Default Konstruktor braucht: Der Compiler braucht den Default Constructor nicht. Brauchen tut er keine dieser Special Member Functions. Er geht aber davon aus, dass du sie brauchst, weil du deine Klasse möglichst vielfältig erzeugen können möchtest.
Du kannst aber das Verhalten Steuern bzw. das, was deine Klasse repräsentieren soll. Nehmen wir einmal an, dass eine erzeugte Instanz deiner Klasse etwas einzigartiges repäsentieren soll, z.B. eine Verbindung zwischen zwei bestimmten IP-Adressen. Ist das etwas, das man kopieren können soll? Die Verbindung besteht ja nur einmal. Du kannst jetzt deinen Copy Constructor und Copy Assignment Operator mittels
= delete
löschen. Somit kann eine Instanz deiner Klasse nur noch "gemoved" werden, nicht mehr kopiert. (Okay, das Beispiel ist etwas lame, aber ich hoffe es wurde klar)
Wenn deine Klasse etwas komplexer sind, kannst du durch eigene Implementierungen dieser Special Member Functions sauber entsprechende Erzeugunsvorgänge beschreiben. Das schafft der Compiler dann nicht mehr automatisch und/oder fehlerfrei. Eine Move-Konstruktion ist eben etwas anders als eine Kopie zu erstellen.
Warum will man überhaupt eine Instanz einer Klasse erstellen, ohne einen Vector rueberzureichen?
Das ist bei einem solch simplen Beispiel eine berechtigte Frage, es kommt wie immer auf deinen konkreten Fall an. Stell dir vor, du hast eine Klasse, die 10 oder 15 Member Variablen enthält. Und für die es nicht wichtig ist, dass alle gesetzt sind, um zu "funktionieren", sondern eher Zusatzinformationen enthält oder alternatives Verhälten abbildet. (Man könnte fragen: Wenn Member7 gesetzt ist, tue dies, wenn nicht, tue jenes)
Was du nun anschauen müsstest, um das in Gänze zu verstehen:
- Wie siehen die Signaturen der Special Member Functions aus?
- Was ist eine R-Value-Referenz (die Move (Assignment) C'tors benutzen diese)
- Beispielimplementierungen
- Wann wird welcher C'tor aufgerufen (das ist nicht immer offensichtlich, ganz im Gegenteil ;-))