Seite 1 von 1

Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 18:40
von BeRsErKeR
Virtuelle Vererbung wird ja meist in Zusammenhang mit Mehrfachvererbung eingesetzt. Hier mal ein Beispiel:

Code: Alles auswählen

class Person {
  string name;
  // ...
};
 
class Mitarbeiter : public virtual Person { /* ... */ };
class Kunde       : public virtual Person { /* ... */ };
 
class MitarbeiterUndKunde : public Mitarbeiter, public Kunde { /* ... */ };
Gehen wir nun mal davon aus, dass jede Klasse einen Konstruktor besitzt, der einen string als Parameter benötigt. Daraus resultiert nun, dass man in der Klasse MitarbeiterUndKunde, den Konstruktor von Person manuell aufrufen muss. Ergibt einen Sinn, da ja nicht klar ist, ob man nun den Person-C'tor-Call von Mitarbeiter oder Kunde nehmen soll. Soweit so gut.

Wenn man nun aber eine neue Klasse einführt (sagen wir mal Chef), die nur von der Klasse Mitarbeiter erbt, so muss scheinbar auch hier der C'tor von Person manuell aufgerufen werden. Ich frage mich nun, ob es einen tieferen Sinn dahinter gibt, oder ob der Compiler einfach nicht schlau genug ist um festzustellen, dass keine Mehrfachvererbung vorliegt und der Person-C'tor-Call aus Mitarbeiter eindeutig ist.

Warum ist das so wichtig? Nun nehmen wir mal an wir haben eine Hierarchy aus vielen Ebenen, die alle nur Einfachvererbung nutzen und irgendwo weit unten in der Hierarchy wurde eine virtuelle Vererbung verwendet. Dann muss man für jede dieser Klassen den Konstruktor von Person aufrufen, wobei man das teilweise lieber den Basisklassen überlassen will.

Gibt es da Lösungen oder plausible Gründe, warum das so gelöst ist? Ist das nicht bei jedem Compiler der Fall? Getestet habe ich das ganze mit dem g++ 4.XX.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 19:46
von grid
Was du hier beschreibst ist das Diamond problem. Wenn man danach sucht findet man einige nützliche Hinweise (z.B. http://en.wikipedia.org/wiki/Diamond_problem), deswegen spare ich mir die Erklärung ;-).

ABER: Oft wird gerade dies als Nachteil für Mehrfachvererbung angegeben, was meiner Meinung nach nur bedingt richtig ist. Denn oft ist eine solche Diamond Shape die Folge von einer nicht durchdachten Klassenhirarchie. In deinem Beispiel müsste man sich überlegen, ob die Klassen Mitarbeiter und Kunde nicht eher Aspekte beschreiben sollten.

Ich hab gerade nicht genug Zeit das genauer auszuführen, aber vielleicht hilft dir das ja schon. Wenn nicht, dann gibts hier ja genug C++ Experten und vlt.schreib ich ja nachhernoch etwas dazu.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 20:13
von Krishty
BeRsErKeR hat geschrieben:Gibt es da Lösungen oder plausible Gründe, warum das so gelöst ist?
(von hier an mutgemaßt)

Weil es konsistenter ist: Wenn man eine virtuelle Vererbung definiert, legt man fest, dass man die Konstruktion der Basis von der zuletzt ableitenden Klasse aus vorgenommen haben will. Immer. Nicht nur dann, wenn es nötig ist. Bei einfacher Vererbung die Konstruktion durch die Basisklasse vornehmen zu lassen würde bedeuten, die Regel für eine Ausnahme zu brechen, nur, um eine Zeile zu sparen. (Dass man trotzdem früher in der Hierarchie den K’tor aufrufen kann, hat den Grund, dass der Compiler nicht sagen kann, ob man die Zwischenstufen der Hierarchie nicht irgendwann instanzieren möchte bzw. wo die Vererbungshierarchie eigentlich zuende ist.)

Weil es einfacher ist: Dadurch hat Vererbung nämlich nur einen zusätzlichen Zustand – virtuell geerbt – statt zweien (virtuell geerbt und virtuell einfach geerbt). Das vereinfacht sowohl den Sprachstandard als auch die Compiler-Implementierung und nicht zuletzt auch die Wartung des Textes (entscheidet man sich, doch noch mehrfach zu erben, muss man nur den Konstruktor der neuen zusätzlichen Basis nachtragen, und nicht wieder den der virtuellen Basis heraussuchen und die Aufgaben, die vorher in der Basis vorgenommen wurden, hochdirigieren).

Alles in allem: Um eine Zeile zu sparen werden keine Regeln gebrochen. Was passiert wenn doch, sieht man an der Pest des explicit-Schlüsselworts.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 21:21
von Sternmull
BeRsErKeR hat geschrieben: Warum ist das so wichtig? Nun nehmen wir mal an wir haben eine Hierarchy aus vielen Ebenen, die alle nur Einfachvererbung nutzen und irgendwo weit unten in der Hierarchy wurde eine virtuelle Vererbung verwendet. Dann muss man für jede dieser Klassen den Konstruktor von Person aufrufen, wobei man das teilweise lieber den Basisklassen überlassen will.
Weil jede Basisklassen den Konstruktor der virtuellen Basisklasse(n) mit anderen Parametern aufrufen könnte. z.B. so hier:

Code: Alles auswählen

#include <string>

class Virtual
{
public:
	int m_blah;

	Virtual(int x)
	{
		m_blah = x;
	}
};

class Base1 : virtual public Virtual
{
	std::string m_base1String;
public:
	Base1(std::string s) : Virtual(1)
	{
		m_base1String = s;
	}
};

class Base2 : virtual public Virtual
{
	std::string m_base2String;
public:
	Base2(std::string s) : Virtual(2)
	{
		m_base2String = s;
	}
};

class Derived : public Base1, public Base2
{
public:
	// Base1 und Base2 initialisieren Virtual mit verschiedenen Werten, die Basisklasse gibts aber nur einmal, also muss man sich wohl oder übel auf einen Wert festlegen
	Derived(std::string s) : Virtual(3), Base1(s), Base2(s)
	{
	}
};

class MoreDerived : public Derived
{
public:
	MoreDerived(std::string s) : Virtual(4), Derived(s) // hier finde ich es allerdigns überflüssig (GCC besteht aber drauf)
	{
	}
};
Base1 und Base2 initialisieren Virtual mit unterschiedlichen Werten. Deshalb finde ich es schon nachvollziehbar das man in Derived explizit angeben muss mit welchem Wert Virtual initalisiert werden soll. Man könnte zwar auch argumentieren das es doch sinnvoll wäre die Initialisierung aus der ersten oder letzte Basisklasse zu verwenden, aber das könnte schnell zu Missverständnissen führen da jeder was anderes für intuitiv halten könnte.
Wo dein Problem aber (für mich eine neue Erfahrung :) ) auftritt ist der Konstruktor von MoreDerived. Hier wird die Initialierung von Virtual ja eigentlich durch den Konstruktor von Derived eindeutig festgelegt. Trotzdem muss man den Virtual-Konstruktor explizit aufrufen.

Es würde mich nicht überraschen wenn es dafür keine Lösung gibt. Allerdings dürfte den meisten Leuten die virtuelle Basisklasse an sich ein Dorn im Auge sein. Die Dinger verwendet kaum einer, und deshalb kennen sie auch viele nicht bzw. machen sich keine Gedanken über die Details. Ich kann mich ehrlich gesagt auch nicht erinnern wann mir mal die Verwendung einer virtuellen Basisklasse untergekommen wäre. Von daher dürfte die praktisch wertvollste Lösung die Vermeidung von virtuellen Basiklassen sein.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 21:24
von Krishty
Soweit ich verstanden habe kritisiert BeRsErKeR nicht, dass man das beim Diamantproblem machen muss, sondern, dass man es selbst dann machen muss, wenn nur eine einzige Klasse erbt:
BeRsErKeR hat geschrieben:Wenn man nun aber eine neue Klasse einführt (sagen wir mal Chef), die nur von der Klasse Mitarbeiter erbt, so muss scheinbar auch hier der C'tor von Person manuell aufgerufen werden. Ich frage mich nun, ob es einen tieferen Sinn dahinter gibt.
Und da bleibe ich dabei dass eine virtuelle Vererbung die Bedeutung hat, die Initialisierung auf abgeleitete Klassen zu dirigieren – selbst, wenn die im Fall einfacher Vererbung der Bequemlichkeit halber nicht selber initialisieren müssten.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 26.01.2011, 23:05
von BeRsErKeR
Krishty hat geschrieben:
BeRsErKeR hat geschrieben:Gibt es da Lösungen oder plausible Gründe, warum das so gelöst ist?
(von hier an mutgemaßt)

Weil es konsistenter ist: Wenn man eine virtuelle Vererbung definiert, legt man fest, dass man die Konstruktion der Basis von der zuletzt ableitenden Klasse aus vorgenommen haben will. Immer. Nicht nur dann, wenn es nötig ist. Bei einfacher Vererbung die Konstruktion durch die Basisklasse vornehmen zu lassen würde bedeuten, die Regel für eine Ausnahme zu brechen, nur, um eine Zeile zu sparen. (Dass man trotzdem früher in der Hierarchie den K’tor aufrufen kann, hat den Grund, dass der Compiler nicht sagen kann, ob man die Zwischenstufen der Hierarchie nicht irgendwann instanzieren möchte bzw. wo die Vererbungshierarchie eigentlich zuende ist.)

Weil es einfacher ist: Dadurch hat Vererbung nämlich nur einen zusätzlichen Zustand – virtuell geerbt – statt zweien (virtuell geerbt und virtuell einfach geerbt). Das vereinfacht sowohl den Sprachstandard als auch die Compiler-Implementierung und nicht zuletzt auch die Wartung des Textes (entscheidet man sich, doch noch mehrfach zu erben, muss man nur den Konstruktor der neuen zusätzlichen Basis nachtragen, und nicht wieder den der virtuellen Basis heraussuchen und die Aufgaben, die vorher in der Basis vorgenommen wurden, hochdirigieren).

Alles in allem: Um eine Zeile zu sparen werden keine Regeln gebrochen. Was passiert wenn doch, sieht man an der Pest des explicit-Schlüsselworts.
Das würde ich so unterschreiben. Das Problem was ich sehe ist, dass man bei einer Mehrfachvererbung, bei der man nur eine Instanz der gemeinsamen Basisklasse nutzen will, gezwungen wird virtuelle Vererbung zu nutzen und sich somit auch den C'tor-Aufruf für einfach abgeleitete Klassen einhandelt. Virtuelle Vererbung kann ja von mir aus bleiben was sie ist, aber eine einfache Vererbung ist nun mal was anderes als eine Mehrfachvererbung und ich möchte nicht das erste Verfahren beeinflussen, nur weil ich das zweite Verfahren nutzen will.

Ich sage ja nicht, dass man die virtuelle Vererbung ändern soll. Aber vielleicht könnte man ja für die Mehrfachvererbung eine Art der Vererbung einfügen, die nicht die Einfachvererbung beeinflusst und dennoch die Funktionalität einer virtuellen Vererbung in diesem Zusammenhang ermöglicht.

Zum Thema "Zeile sparen". Mir geht es nicht nur um den Schreibaufwand, sondern auch darum, dass die letzte Klasse in der Hierarchy nicht unbedingt etwas von der darunter liegenden Hierarchy wissen muss. Sagen wir mal ich habe eine Klasse Object, die einfach nur einen Typ-String speichert und leite davon eine Klasse ab, die diesen Typ-String automatisch auf einen gewissen Wert setzt. Dann möchte ich bei einer Klasse 10 Ebenen weiter hinten nicht noch rausfinden, welchen Typ-String ich da an Object übergeben muss, da mir das dann völlig egal sein kann. Allerdings ist auch das "Zeile sparen"-Problem nicht ganz ohne. Wenn man riesige Hierarchien hat, die bislang mit einfacher Vererbung auskamen und ich implementiere eine neue Klasse, die eine virtuelle Mehrfachvererbung braucht, dann muss ich eventuell bei etlichen Klassen den C'tor-Call der Basisklasse einfügen. Bei großen Projekten kann das schon in Arbeit ausarten. Klar könnte man da vielleicht besser im Vorraus planen, aber letztlich kann man sich nie sicher sein.


@Sternmull: Bei Mehrfachvererbung verstehe ich das schon (habe ich ja auch geschrieben). Aber nicht bei Einfachvererbung.

grid hat geschrieben:Was du hier beschreibst ist das Diamond problem. Wenn man danach sucht findet man einige nützliche Hinweise (z.B. http://en.wikipedia.org/wiki/Diamond_problem), deswegen spare ich mir die Erklärung ;-).
Mit dem Diamond Problem habe ich kein Problem, sondern eher mit den Auswirkungen von virtueller Vererbung bei einfacher Vererbung. Ich möchte nicht wirklich die virtuelle Vererbung nutzen, brauche sie aber bei der Mehrfachvererbung um nicht 2 Instanzen der Basisklasse zu haben. Bei der einfachen Vererbung muss ich sie dann auch notgedrungen verwenden und handle mir Dinge ein, die ich nicht möchte.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 27.01.2011, 07:20
von Kahino
Für mehrfachvererbung braucht man nicht zwangsläufig virtuelle Basisklassen.
Erforderlich ist das ausschließlich dann wenn Basen indirekt mehrfach vorhanden wären.
Und eine Situation wie in deinem Beispiel gäbe es vielleicht bei der OOA, im OOD sähe das sicher etwas anders aus.

Aber mal zum Problem:
Grund für diesen Umstand ist eine Verallgemeinerte Handhabung bei der Mehrfachvererbung.
Bei der Einfachvererbung und der Merhfachvererbung ohne indirekte gemeinsame Basen, ist das ganze ziemlich gerade aus.
Würde man ein Diagramm zeichen, würde der Baum von Oben nach unten, von links nach rechts erstellt.
Der Aufruf der Initialisierer, geschiet jedoch von Unten nach oben.

Bei der Mehrfachvererbung gibt es jetzt jedoch zu viele Variablen. Zum einen bist du nicht auf zwei Basen beschränkt und zu m anderen hast du keinen schimmer wie die Basen wohl genau aussehen könnten, ob nun bestimmte klassen Indirekt mehrfach vorkommen oder nicht. Dazu kommt noch, das gemeinsamen Basen ja nicht immer nur so angeordnet sind wie in den Beispielen die man zur Demonstration so findet. Die eventuellen gemeinsamen Basen könnten ja auch auf mehrere Ebenen versetzt vorkommen odar gar auf der gleichen Linie(bei einer ausreichend großen Verzweigung).

Damit man dort nun eindeutig mit diesen gemeinsamen Basen vorgehen kann, wurde eben entschieden, dass virtuelle Basen prinzipiell zuerst erstellt werden sollten, unabhängig wo sie in der Hierarchie zu finden sind (damit alle von dieser Klasse abhängigen klassen auch ja auf die gleichen Daten zugreifen).
Doch wird durch dieses Verhalten die Kette der Initialisierer gebrochen. Daher entstand noch als Nebeneffekt,
dass alle Initialiserer virtueller Basen in der Hierarchie ignorriert werden und in die klasse verlagert werden,
die inder Hierarchie ganz unten ist. Eben die zuletzt erstellt klasse. DiePerson welche die erstellt ,wird am besten wissen wie die Hierarchie aussehen soll.

ass die letzte Klasse in der Hierarchy nicht unbedingt etwas von der darunter liegenden Hierarchy wissen muss
Jetzt mal unabhängig von diesem Vererbungsproblem, finde ich diese Aussage ein wenig seltsam.
Du musst nichst von der Implementierung der zu Grunde liegenden Hierarchie wissen,
doch wenn wir von Konstruktoren reden, reden wir von einem Bestandteil der Spezifikation (welche Größtenteils ja von der Programmierung unabhängig ist).
Und Spezifikikationen werden stets mitvererbt.
Das heißt, die Spezifikation der Basis ist neben der Ableitungseigenen, stets auch Spezifikation der Ableitung. Sie darf sich zwar aufweichen, wird vom Grundaufbau jedoch immer mitgetragen. Das heißt, wenn du wissen möchtest was du benötigst, schaust du dir einfach die Basen an von denen du ableitest, keine 10 Ebenen höher oder so.


Ob das nun schick ist oder nicht, dein Problem dabei verstehe ich jetzt irgendwie nicht.
Basen werden nicht prophilaktisch virtuell, das ist eine Designeentscheidung.
Und da Designeentscheidungen eine gewisse Planung voraussetzen, wirst du von vorneherein doch zu mindest abschätzen können ob eine virtuelle Ableitung nötig ist oder nicht. Und wenn sie es ist, wird auf kurz oder lang sowieso eine Mehrfachvererbung entstehen in der diese Klasse merhfach als Basis auftaucht.
Hier sei nochmal erwähnt, das Merhfachvererbung nicht zwangsläufig eine virtuelle Ableitung benötigt.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 27.01.2011, 15:49
von BeRsErKeR
Kahino hat geschrieben:
ass die letzte Klasse in der Hierarchy nicht unbedingt etwas von der darunter liegenden Hierarchy wissen muss
Jetzt mal unabhängig von diesem Vererbungsproblem, finde ich diese Aussage ein wenig seltsam.
Du musst nichst von der Implementierung der zu Grunde liegenden Hierarchie wissen,
doch wenn wir von Konstruktoren reden, reden wir von einem Bestandteil der Spezifikation (welche Größtenteils ja von der Programmierung unabhängig ist).
Und Spezifikikationen werden stets mitvererbt.
Das heißt, die Spezifikation der Basis ist neben der Ableitungseigenen, stets auch Spezifikation der Ableitung. Sie darf sich zwar aufweichen, wird vom Grundaufbau jedoch immer mitgetragen. Das heißt, wenn du wissen möchtest was du benötigst, schaust du dir einfach die Basen an von denen du ableitest, keine 10 Ebenen höher oder so.


Ob das nun schick ist oder nicht, dein Problem dabei verstehe ich jetzt irgendwie nicht.
Basen werden nicht prophilaktisch virtuell, das ist eine Designeentscheidung.
Und da Designeentscheidungen eine gewisse Planung voraussetzen, wirst du von vorneherein doch zu mindest abschätzen können ob eine virtuelle Ableitung nötig ist oder nicht. Und wenn sie es ist, wird auf kurz oder lang sowieso eine Mehrfachvererbung entstehen in der diese Klasse merhfach als Basis auftaucht.
Hier sei nochmal erwähnt, das Merhfachvererbung nicht zwangsläufig eine virtuelle Ableitung benötigt.
Überleg mal wenn man Klasse A, B und C hat. A ist die Basisklasse, B erbt virtuell von A und C erbt von B. A und B haben einen Konstruktor, dem man einen Typ-String übergibt. Bs C'tor hat zusätzliche Parameter. Nun muss ich von C den C'tor von B sowieso aufrufen mit z.B. dem Typ-String "MeinTyp". B ruft selbst den C'tor von A mit dem übergebenen Typ-String auf.

Ich muss nun durch die virtuelle Vererbung zweimal den gleichen Typ-String übergeben, einmal an den C'tor von A und einmal an den C'tor von B. Das gefährliche daran ist, dass ich A und B verschiedene Typ-Strings übergeben kann (was kein gewolltes Verhalten ist). Der Typ-String-Parameter von B wird komplett überflüssig.

Außerdem: Dass die Mehrfachvererbung keine virtuelle Vererbung voraussetzt habe ich selbst erwähnt. Wenn man aber den Fall hat, wo man nur eine Version eine Basisklasse haben will und diese mehrfach in den Basisklassen enthalten ist (diesen Fall hat man sicherlich recht häufig bei Mehrfachvererbung), wird einem die virtuelle Vererbung aufgezwungen.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 27.01.2011, 16:16
von Kahino
Ich muss nun durch die virtuelle Vererbung zweimal den gleichen Typ-String übergeben, einmal an den C'tor von A und einmal an den C'tor von B. Das gefährliche daran ist, dass ich A und B verschiedene Typ-Strings übergeben kann (was kein gewolltes Verhalten ist). Der Typ-String-Parameter von B wird komplett überflüssig.
Genau das passiert ja eben nicht (also das mit den unterschiedlichen übergaben).
Hier noch mal der relevante Teil aus meinem Post
Damit man dort nun eindeutig mit diesen gemeinsamen Basen vorgehen kann, wurde eben entschieden, dass virtuelle Basen prinzipiell zuerst erstellt werden sollten, unabhängig wo sie in der Hierarchie zu finden sind (damit alle von dieser Klasse abhängigen klassen auch ja auf die gleichen Daten zugreifen).
Doch wird durch dieses Verhalten die Kette der Initialisierer gebrochen. Daher entstand noch als Nebeneffekt,
dass alle Initialiserer virtueller Basen in der Hierarchie ignorriert werden und in die klasse verlagert werden,
die inder Hierarchie ganz unten ist. Eben die zuletzt erstellt klasse. DiePerson welche die erstellt ,wird am besten wissen wie die Hierarchie aussehen soll.
In deinem ABC Beispiel, in dem von A virtuell geerbt wird, ist C das letzte Glied in deiner Hierarchie, das heißt der Initialisierer für A in B wird ignorriert. Da kansnt du übergeben was du magst. Denn A wird erstellt, bevor der Rest der Hierarchie angegangen wird. Verwendet wird dafür der Initialisierer im Letzten Glied der Hierarchie.
Nun gut, von der Reihenfolge her ändert sich da ja nichts bezüglich einer nicht virtuellen Ableitung.
Zum Verständnis dann mal eine abgewnadelte Form deines Beispiels

A,B, C,D
A ist Basis B. B sei die virtuelle klasse von der C ableitet. D erbt von C.
Ansonsten das gleiche Szenario.
Erstellst du nü ein D-Objekt, wird noch vor dem A-Konstruktor der B Konstruktor ausgeführt, anhand des Initialierers in D.
Eventuelle Initialiserer für B aus C (und seien sie von D aus weiter geleitet) werden ignorriert.

Virtuelle KLassen werden verarbeiter bevor der "normale" Durchlauf beginnt:
"Initialiserer von unten nach oben, KOnstruktion von oben nach unten".
Dass die Mehrfachvererbung keine virtuelle Vererbung voraussetzt habe ich selbst erwähnt
sry, hab ich wohl überlesen.

Re: Virtuelle Vererbung und C'tor-Calls

Verfasst: 27.01.2011, 20:38
von BeRsErKeR
Wie schon gesagt ich will die virtuelle Vererbung nicht ändern. Sie hat ihre Daseinsberechtigung. Aber ich möchte sie nicht für Einfachvererbung nutzen müssen, nur weil ich eine Mehrfachvererbung (mit einer Basis-Instanz) drin hab. Zumal es vom logischen Aufbau her möglich wäre, dort ohne auszukommen.