Bietet Private-Vererbung irgendwelche Vorteile gegenüber Komposition?
-
Kann es nicht sinnvoll sein, auf diese Weise Codeduplizierung zu vermeiden? Oder hältst du eine Aggregation immer für angebrachter?
Ein ausgedachtes Beispiel: Zwei Klassen in einem GUI-Framework,
PanelundWindow.Panelist eine Unterteilung des Fensters und kann selber beliebige GUI-Komponenten (auch anderePanels) aufnehmen.Windowsoll das auch können, aber es muss zuoberst stehen und darf nicht als Bestandteil anderer Komponenten fungieren (darf also nicht alsPanelbehandelt werden). Deshalb könnte es privat vonPanelerben und die gemeinsamen Funktionen öffentlich machen.Hmm... Ganz sauber kommt mir das selber nicht vor, zumal hier gegen Aggregation nicht wirklich etwas spricht.

Ich wäre trotzdem froh um Meinungen dazu, mir fällt leider kein besseres Beispiel ein. Wenn die Beschreibung etwas ungenau war, kann ich das Beispiel gerne an einem Stück Code illustrieren.
-
Hmm. Ich würde das wahrscheinlich eher so lösen, dass ich eine Klasse
Containerdefiniere und dann kannst du da Window und Panel davon ableiten, da beide als Container fungieren und somitContainersind. (Das ist ja deine grundsätzliche Aussage oben dasWindowundPanelContainer sind).
-
Man kann damit Designschwächen anderer Bibliotheken ausbügeln:
Nämlich immer dann, wenn man eine Aggregation will, aber protected member des Objektes braucht, dann ist die private Vererbung das Einzige, was einen rettet.
-
otze schrieb:
Man kann damit Designschwächen anderer Bibliotheken ausbügeln
Jup. Wohl die Hauptanwendung.
-
drakon schrieb:
Hmm. Ich würde das wahrscheinlich eher so lösen, dass ich eine Klasse
Containerdefiniere und dann kannst du da Window und Panel davon ableiten, da beide als Container fungieren und somitContainersind. (Das ist ja deine grundsätzliche Aussage oben dasWindowundPanelContainer sind).Panelwürde aber von einer zusätzlichen (abstrakten) Basisklasse für alle GUI-Komponenten erben, sagen wirComponent. Elementare Komponenten (nennen wir sie Widgets) wie Schaltflächen werden ebenfalls von dieser Klasse abgeleitet.Wäre hier Mehrfachvererbung angebracht?
+-----------+ +-----------+ | Container | | Component | +-----------+ +-----------+ ^ ^ | | +------+-------+ +-------+-------+ | | | | +-----------+ +-----------+ +-----------+ | Window | | Panel | | Widget | +-----------+ +-----------+ +-----------+
-
Ja, Mehrfachvererbung empfinde ich durchaus als einen guten Lösungsansatz. (Potentiell gibt es sogar noch mehr, von denen du erben kannst, wenn man wirklich eine GUI Library erstellt).
-
drakon schrieb:
Potentiell gibt es sogar noch mehr, von denen du erben kannst, wenn man wirklich eine GUI Library erstellt.
Ich will nicht eine komplette GUI schreiben, eher was Kleines für SFML, vor allem für alltägliche Dinge. Erweitern kann ich es immer noch. Gerade auch deshalb habe ich mir vorgenommen, von Anfang an auf schönes Design zu achten.
Klar könnte man noch mehr machen. Ich versuche aber, allzu tiefe Klassenhierarchien zu vermeiden und die Zusammenhänge - sofern es geht - einfach zu halten. Im Gegensatz zu vielen GUIs in C++ möchte ich dem Anwender auch die Möglichkeit geben, die Komponenten selbst zu verwalten. Also vorerst nichts mit Pseudo-Garbage-Collector.

Das mit dem
Containermuss ich mir noch überlegen. So eine abstrakte Basisklasse macht vor allem Sinn, wenn man verschiedene abgeleitete Klassen gemeinsam ansprechen will, dafür fehlen mir gerade Anwendungsfälle. Was ich mir auch überlege, ist eine Aggregation, also dass das Fenster ein Standard-Panel besitzt (das eben das ganze Fenster ausfüllen würde). Um an diesem Änderungen vorzunehmen, könnte ich entweder Methoden wrappen, eine Referenz auf das interne Panel zurückgeben (nicht besonders sauber) oder eben private Vererbung benutzen. Letzteres scheint mir aber wie gesagt ebenfalls nicht ganz das Wahre zu sein...Oder ich lasse den Benutzer das Panel selbst erstellen und verweise im Fenster lediglich darauf. Somit ist er flexibler. Das ist allerdings mühsam, weil man zu jedem Fenster gleich ein Panel erstellen muss, um es überhaupt benutzen zu können.
-
Die Sache mit der privaten Vererbung, um dann einfach per using die geerbten Methoden zu veröffentlichen ist natürlich sehr verlockend, weil man dann nicht die ganzen Methoden nach Schema F definieren und an das Member durchreichen muss. Die Semantik sit wie volkard gesagt hat aber eben eine ganz andere.
Für sowas würde ich mir ein neues Sprachelement wünschen:class A { int foo(); char foo(int i); string bar(int a, bool b, char c); }; //böse: class B : private A { public: using A::foo; using A::bar; }; //richtig, aber aufwändig und langatmig: class C { A theA; public: int foo() { return theA.foo(); } char foo(int i) { return theA.foo(i); } string bar(int a, bool b, char c) { return theA.bar(a,b,c); } }; //schön wärs: class D { A theA; public: expose theA.foo; expose theA.bar; };
-
Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.
Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)? Dann wäre auch an Nexus' Vorschlag der Übernahme von Funktionen privater Oberklassen per using nur noch wenig auszusetzen.
Der Sinn von expose erschließt sich mir nicht. Was ist der Vorteil gegenüber der using-Lösung? Abgesehen halt vom IST-EIN-"Problem".
Stefan.
-
DStefan schrieb:
Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.
Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)? Dann wäre auch an Nexus' Vorschlag der Übernahme von Funktionen privater Oberklassen per using nur noch wenig auszusetzen.der main() kannst du sowas meinetwegen erzählen. aber glaubt die erbende klasse das denn auch? klares nein.
und wozu erben, wenn's nicht not tut? das übt doch nur unnötig druck auf die klassenhierarchie (taxonomie!) aus und führt dann zu mehrfachen erblichkeitsproblemen.
-
Ich hab letztens private Vererbung hierfür eingesetzt:
class ENTITYSYSTEM_API EntityManager : private MessageClient { ... };class ENTITYSYSTEM_API MessageClient : boost::noncopyable { protected: // Subscribes to receive messages of a specific type. void Subscribe(const MessageType&); // Unsubscribes to receive messages of a specific type. void Unsubscribe(const MessageType&); // Processes a received message. It is guaranteed that no message is received in which this // message client is not interested. virtual void ProcessMessage(const Message&) = nullptr; };Die Idee hier ist, dass der EntityManager am Message-System teilnehmen möchte, hierzu muss er eine virtuelle Methode overriden. Es ist aber keinesfalls gewünscht, dass jemand von außen den EntityManager für irgendwas subscriben kann, sondern der EntityManager weiß selber genau, was für Messages er braucht.
Daher fällt öffentliche Vererbung aus. Komposition wäre umständlich, ich müsste erst von MessageClient ableiten und eine Instanz dieser konkreten Klasse als member aufnehmen. Daher finde ich das hier eine brauchbare Lösung (andere Meinungen mit Begründung sind willkommen).
Trotzdem zieht man im Allgemeinen Komposition natürlich vor. Schlauerweise nimmt man immer die schwächste Verbindung, die noch sinnvoll ist, also Aggregation, Komposition, Vererbung und nur zuletzt friend.
-
Optimizer schrieb:
virtual void ProcessMessage(const Message&) = nullptr;Da ist auch nullptr erlaubt?
-
Optimizer schrieb:
Ich hab letztens private Vererbung hierfür eingesetzt:...
Alternativ könnte man überlegen, nicht nur Empfängerobjekte zu registrieren, die je nach Nachricht eine bestimmte virtuelle Funktion anbieten müssen, sondern beliebige Objekte/Methodenzeiger-Paare registrieren zu können.
-
DStefan schrieb:
Ich finde es nicht nachvollziehbar, sich im Falle privater Vererbung mit der Definition "IST-EIN" aufzuhalten und dann von diesem Standpunkt aus private Vererbung zu beurteilen.
Im Falle öffentlicher Vererbung halte ich IST-EIN für ein brauchbares Kriterium, bei privater Vererbung aber ist es Unsinn. Wie wäre es stattdessen mit IST-IMPLEMENTIERT-MIT (oder ähnlichem)?
Dann schon "IST-IMPLEMENTIERT-MIT-UND-FÜR-SICH-SELBST-UND-IM-KREISE-DER-FREUNDE-IST-EIN" - für sich selbst und für alle friends gilt nunmal die Ist-Ein-Beziehung. Und die wegzudiskutieren hilft auch nicht dagegen dass es semantisch eben etwas anderes ist als Aggregation.
DStefan schrieb:
Der Sinn von expose erschließt sich mir nicht. Was ist der Vorteil gegenüber der using-Lösung? Abgesehen halt vom IST-EIN-"Problem".
Eben. Abgesehn von der Typsicherheit kann man auch Makros an Stelle vieler Templates benutzen. Man kann für viele Schwächen der Sprache Workarounds finden, die super funktionieren auch wenn sie eigentlich was anderes bedeuten.
btw:
class E { A someA; A anotherA; public: expose someA.foo; expose anotherA.bar; };nicht machbar mit Vererbung

-
volkard schrieb:
Optimizer schrieb:
virtual void ProcessMessage(const Message&) = nullptr;Da ist auch nullptr erlaubt?
Ich hoffe es. Im Moment ist es nur ein #define.
-
Optimizer schrieb:
Ich hoffe es.
Du hast =0 gelernt, =0 steht im Standard, andere haben =0 gelernt.
Wozu abweichen? Purer Flagellantismus?Ganz verwundert bin über das rätselhafte =nullptr (oder =0x0). Wenn schon, dann mach
#define abstract =0dann hätte das noch ne lesbare Aussage.
noch besser:
virtual void ProcessMessage(const Message&) = 0; //abstract
-
Ich habe bisher immer so gedacht, = 0 wäre gedanklich der Weg, um nen Funktionspointer (was anderes ist der Name der Funktion ja nicht) ungültig sein zu lassen. Bisher war = 0 auch der Weg, um nullptr jeglicher Art zu erhalten.
Anscheinend ist aber auch weiterhin nur = 0 zulässig was ich etwas inkonsequent finde. Mit einem Integer eine nicht implementierte Funktion zu beschreiben, erscheint mir nicht logisch.
-
volkard schrieb:
noch besser:
virtual void ProcessMessage(const Message&) = 0; //abstractDer Trivialkommentar ist nicht besser, ich schreibe keine Kommentare für Leute, die C++ nicht grundsätzlich lesen können. Das ist für mich auf der selben Ebene wie:
int a = b + c; // here, we add b and c
-
Optimizer schrieb:
Mit einem Integer eine nicht implementierte Funktion zu beschreiben, erscheint mir nicht logisch.
Ja, man wollte halt nicht noch ein Schlüsselwort einführen, was ein wenig schade ist. Dieses =0 mit abstrakten Funktionen und abstrakten Basisklassen ist schon ein gewaltiger Brocken und hat mehr Zuwendung verdient.
-
In Hinsicht auf den neuen Standard würde ich -- wenn ich mich von 0 abwende -- wohl eher sowas machen:
#define abstract 0Dann bleibt man einheitlich gegenüber verbotenen und standardgenerierten Funktionen:
class MyClass { virtual void Foo() = abstract; MyClass() = default; MyClass& operator= (const MyClass&) = delete; };Obwohl ich das Schlüsselwort-Recycling ziemlich schlimm finde, vor allem bei
delete. Meines Erachtens sind mehr Schlüsselwörter besser als mehrdeutige, vor allem wenn die Anwendungsbereiche sich so stark wie hier unterscheiden (beistatickönnte man sich darüber streiten...).