Initialisierung bei virtueller Basisklasse
-
Hallo zusammen,
ich bin mir nicht ganz sicher, ob die Benutzung von virtuellen Basisklassen kapiert habe, daher frage ich einfach euch.
Folgender Code:
#include<iostream> class Basis { public: Basis() {std::cout << "Basis-Standardkonstruktor\n";} Basis(const char* a) {std::cout << a << std::endl;} virtual ~Basis() {} }; class Links : virtual public Basis { public: Links(const char* a) : Basis(a) {} }; class Rechts : virtual public Basis { public: Rechts(const char* a) : Basis(a) {} }; class Unten : public Links, public Rechts { public: Unten(const char* a) : Basis(a), Links(a), Rechts(a) {} }; int main() { Unten un("Unten"); Links li("Links"); }1. Erbt die Klassen "Unten" nicht virtual, weil durch das virtual-Vererben ihrer Oberklassen das Problem mit mehreren Subobjekten schon gelöst wurde?
2. Ist es richtig, dass wegen der virtual-Vererbung beim Aufruf "Unten un("Unten");" kein Links::Basis(a) und kein Rechts::Basis(a) durch den Unten-Konstruktor aufgerufen wird?Danke für eure Antworten.

-
Weil eine der Basisklassen von Unten (in diesem Falle sogar beide) von der Basis Basis virtuell erbt, tut dies indirekt auch die Klasse Unten selbst. Virtuelle (direkte und indirekte) Basisklassen werden vom Konstruktor des vollständigen Objektes direkt (und vor allen anderen Basisklassen) initialisiert. Kleines Demo:
#include <iostream> #include <string> using namespace std; struct A { A(int = 0) { cout << "body of A::A(int)\n"; } }; struct B : virtual A { B(int = 0) : A( ( cout << "B::B(int) calling A::A(int)\n", 0 ) ) { cout << "body of B::B(int)\n"; } }; struct C : virtual A { C(int = 0) : A( ( cout << "C::C(int) calling A::A(int)\n", 0 ) ) { cout << "body of C::C(int)\n"; } }; struct D : B, C { D(int = 0) : A( ( cout << "D::D(int) calling A::A(int)\n", 0 ) ), B( ( cout << "D::D(int) calling B::B(int)\n", 0 ) ), C( ( cout << "D::D(int) calling C::C(int)\n", 0 ) ) { cout << "body of D::D(int)\n"; } }; struct E : A // nicht virtuell { E(int = 0) : A( ( cout << "E::E(int) calling A::A(int)\n", 0 ) ) { cout << "body of E::E(int)\n"; } }; struct F : B, C, E // besitzt zwei Subobjekte vom Typ A: // eine virtuelle Basis durch B und C und // eine indirekte nichtvirtuelle Basis durch E (wir könnten auch direkt von A erben, // dann wäre aber jede explizite Verwendung von A mehrdeutig; bereits hier müssen wir qualifizieren: { F(int = 0) : B::A( ( cout << "F::F(int) calling A::A(int)\n", 0 ) ), B( ( cout << "F::F(int) calling B::B(int)\n", 0 ) ), C( ( cout << "F::F(int) calling C::C(int)\n", 0 ) ), E( ( cout << "F::F(int) calling E::E(int)\n", 0 ) ) { cout << "body of F::F(int)\n"; } }; int main() { cout << "****A a; ***\n"; A a; cout << "****B b; ***\n"; B b; cout << "****C c; ***\n"; C c; cout << "****D d; ***\n"; D d; cout << "****E e; ***\n"; E e; cout << "****F f; ***\n"; F f; }
-
camper schrieb:
struct B : virtual A
{
B(int = 0) : A( ( cout << "B::B(int) calling A::A(int)\n", 0 ) ) { cout << "body of B::B(int)\n"; }
};Was zum?! Trickst du beim ersten cout mit dem Komma-Operator herum oder wie?
-
Badestrand schrieb:
camper schrieb:
struct B : virtual A
{
B(int = 0) : A( ( cout << "B::B(int) calling A::A(int)\n", 0 ) ) { cout << "body of B::B(int)\n"; }
};Was zum?! Trickst du beim ersten cout mit dem Komma-Operator herum oder wie?
genau. Es ging mir darum zu demonstrieren, welcher Konstruktor welchen aufruft. Das sollte kein Vorschlag sein, wie Code im Allgemeinen zu schreiben ist.
-
camper schrieb:
genau. Es ging mir darum zu demonstrieren, welcher Konstruktor welchen aufruft. Das sollte kein Vorschlag sein, wie Code im Allgemeinen zu schreiben ist.
Ok, ich hatte mich nur ziemlich gewundert, das sah so ungewohnt aus

-
Also, erstmal danke für eure Antworten, besonders für das umfangreiche Codebeispiel.
Jetzt kommt der Teil mit der Fragerei:

Meine beiden Fragen lassen sich also mit Ja beantworten, oder? Nur das vollständige Objekt darf die Basisklasse initialisieren und es kann auch implizit virtual weitervererbt werden.Was ich nicht verstanden habe:
1. die Funktion des Kommaoperators, der Null und des inneren Klammernpaars
2. den Parameter "int = 0" der Konstruktoren
3. die Bedeutung von "qualifizieren" (Bevorzugung von B::A(...), um Namenskonflikte beim Mehrfacherben von einem virtual und einem nicht-virtual Zweig zu vermeiden?)Danke für eure Geduld

-
moagnus schrieb:
1. die Funktion des Kommaoperators, der Null und des inneren Klammernpaars (Übergabe des ausgegebenen Wertes?)
Der Komma-Operator wertet seine Argumente nacheinander aus und verwirft das Ergebnis des ersten Arguments - den kann man verwenden, wenn man mehrere "Anweisungen" an einer Stelle benötigt, wo nur ein Ausdruck erlaubt ist.
In den Beispielen wird also erst der Text ausgegeben und danach die 0 an den Vater-Ctor übergeben.
(die doppelten Klammern sind nötig, um den Komma-Operator von dem Komma in einer Argumentliste zu unterscheiden - mit einfachen Klammern würde der Compiler einen CTor 'A(ostream&,int)' suchen.2. der Parameter "int = 0" der Konstruktoren
Das nennt sich Default-Parameter - und dürfte hier nötig sein, um den Basis-Ctor'en überhaupt etwas mitgeben zu können.
3. die Bedeutung von "qualifizieren" (Bevorzugung von B::A(...), um Namenskonflikte beim Mehrfacherben von einem virtual und einem nicht-virtual Zweig zu vermeiden?)
"qualifizieren" bedeutet, daß du angeben mußt, welches A du verwenden willst. In der Klasse F hast du zwei davon zur Auswahl und mußt deshalb angeben, ob du den Teil aus der B oder aus der E verwenden willst.
-
moagnus schrieb:
1. die Funktion des Kommaoperators, der Null und des inneren Klammernpaars
Der Kommaoperator fuehrt beide Anweisungen aus, das Ergebnis ist das der zweiten Anweisung. Sprich:
bla,0fuehrt erst bla aus, dann die 0 (wird nicht wirklich ausgefuehrt) und gibt dann die 0 zurueck. Im obigen Beispiel ist bla eine Ausgabe.2. den Parameter "int = 0" der Konstruktoren
Bedeutet dass die Konstruktoren einen int Parameter uebernehmen koennen, aber nicht muessen (fuers "nicht muessen" der default 0). Der Parameter hat keinen Namen, weil er schlichtweg nicht benutzt wird. ein
B(int i = 0)ist vielleicht gelaeufiger.3. die Bedeutung von "qualifizieren" (Bevorzugung von B::A(...), um Namenskonflikte beim Mehrfacherben von einem virtual und einem nicht-virtual Zweig zu vermeiden?)
Jein. Da im Objekt zwei Unterobjekte vom Typ A vorhanden sind, reicht ein einfacher Konstruktoraufruf von A nicht. Qualifizieren bedeutet in diesem Fall, anzugeben, welches A gemeint ist. B::A und C::A ist durchs virtuelle erben das selbe, E::A ist das zweite Vorkommen
-
Danke für deine Antwort.
Zu 1)
( cout << "B::B(int) calling A::A(int)\n", 0 )Es wird also eine Stringausgabe ausgeführt, die Null wird "ausgeführt" und dann wird die Null zurückgegeben? Was bedeutet "Null zurückgeben"? Der Compiler behauptet übrigens, dass "cout" in dem Gültigkeitsbereich der Konstruktoren nicht definiert wäre.
Zu 2) Ahja, ok. Der fehlende Variablenname hat mich irritiert. "int i = 0" ist mir bekannt.
Zu 3)F(int = 0) : B::A( ( cout << "F::F(int) calling A::A(int)\n", 0 ) ), B( ( cout << "F::F(int) calling B::B(int)\n", 0 ) ), C( ( cout << "F::F(int) calling C::C(int)\n", 0 ) ), E( ( cout << "F::F(int) calling E::E(int)\n", 0 ) )Also wird hier B::A(...) gewählt?
Und nochwas: Bei Strukturen statt Klassen wird standardmäßig public vererbt, oder?
-
moagnus schrieb:
Danke für deine Antwort.
Zu 1)
( cout << "B::B(int) calling A::A(int)\n", 0 )Es wird also eine Stringausgabe ausgeführt, die Null wird "ausgeführt" und dann wird die Null zurückgegeben? Was bedeutet "Null zurückgeben"? Der Compiler behauptet übrigens, dass "cout" in dem Gültigkeitsbereich der Konstruktoren nicht definiert wäre.
"ausgeführt" ist vielleicht der falsche Begriff - "ausgewertet" trifft es besser.
OK, dann zurück zu den Grundlagen: Jeder Ausdruck hat einen Wert, der an der nötigen Stelle berechnet wird, d.h. bevor der Ctor aufgerufen werden kann, wird erstmal sein Parameter berechnet. Und diese Berechnung läuft im Fall des Komma-Operators so ab:
- berechne den ersten Operanden
(das ist in diesem Fall das 'cout<<"...", das als Nebeneffekt den Text ausgibt) - berechne den zweiten Operanden
(das ist hier der Ausdruck "0" - mit dem wenig überraschenden Wert 0) - liefere den Wert des zweiten Operanden zurück
(also die "berechnete" 0 - der Wert des ersten Operanden (cout) fällt unter den Tisch)
Btw, kannst du die Fehlermeldung mal komplett wiederholen? (möglich, daß du einfach nur das std:: vergessen hast)
Und nochwas: Bei Strukturen statt Klassen wird standardmäßig public vererbt, oder?
Ja, wird es.
- berechne den ersten Operanden
-
Ok, jetzt hab ichs kapiert.
Hm, ich red von deinem Code...test2.cpp:1: Fehler: expected constructor, destructor, or type conversion before »<« token
/usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/i486-linux-gnu/bits/c++config.h:47: Fehler: »__gnu_debug_def« ist kein Namensbereichs-Name
/usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/i486-linux-gnu/bits/c++config.h:47: Fehler: expected namespace-name before »;« token
test2.cpp: In constructor »A::A(int)«:
test2.cpp:7: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In constructor »B::B(int)«:
test2.cpp:12: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In constructor »C::C(int)«:
test2.cpp:17: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In constructor »D::D(int)«:
test2.cpp:22: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In constructor »E::E(int)«:
test2.cpp:30: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In constructor »F::F(int)«:
test2.cpp:39: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
test2.cpp: In function »int main()«:
test2.cpp:48: Fehler: »cout« wurde in diesem Gültigkeitsbereich nicht definiert
-
Dann schau dir doch mal die erste Zeile der Fehlermeldung an. Offenbar hast du den Compiler schon in der ersten Zeile von test2.cpp verwirrt, dann passierts oft, dass er in den folgenden Zeilen nur noch mehr verwirrt wird, obwohl da nichts dran suzusetzen ist.
-
Also ich würde mal spontan vermuten, du hast das # von '#include<iostream> vergessen.
-

Hätte die Nacht nicht durchmachen sollen (Ausrede)...
Funktioniert natürlich wunderbarDanke nochmals an alle. Hab gut was dazugelernt
