Wozu abstrakte Basisklassen?
-
Hallo,
ich habe in meinem C++ Buch das Kapitel über Polymorphie durchgelesen, und vom Prinzip her sehr gut verstanden. Jedoch habe ich Schwierigkeiten damit, realistische Anwendungsbeispiele für abstrakte Basisklassen zu finden. Wann kann/sollte man solche abstrakten Klassen einsetzen? Ich brauche nur ein paar sinnvolle Anwendungsbeispiele, aber bitte keine Beispiele nach dem Auto/Motor und sonstwas Muster. Am besten an einem praktischen Programmbeispiel näher gebracht. Vielen Dank schonmal!
-
class Serializable { public: virtual void ~Serializable() const = 0; template<class Stream> virtual Stream& serialize(Stream& stream) = 0; }; Serializable::~Serializable() { }Mit abstrakten Basisklassen kannst du Interfaces anbieten. In diesem Beispiel gebe ich Klassen, die von Serializable erben, eine Schnittstelle, mit deer ich sie serialisieren (z. B. in eine Datei speichern) kann.
Der Sinn ist also, einen einheitlichen Namen und eine einheitliche Schnittstelle für eine Operation zu geben, die für mehrere Klassen gleich ist. Ich kann ja alles mögliche in eine Datei speichern wollen. Momentan bastel ich mir was im Info-Unterricht, wo ich die Klassen Entry, abstract Item, Image : Item, Text : Item, Link : Item hab. Wenn das Programm nun beendet wird, soll die gesamte Struktur in eine Datei gespeichert werden.
Nun könnte ich den Ansatz wählen, dass ich durch die Objekte durchiteriere und versuche, jedes Objekt irgendwie in Text zu verwandeln. Das hat vor allem die Nachteile, dass es ein Riesenverwaltungsapparat wird und ich außerdem auch noch alle Typen kennen muss (-> Abhängigkeiten). Wenn ich jetzt einen neuen Typ Tabelle einfüge und vergesse, ihn in der zentralen Speicherungseinheit dazuzuschreiben, hab ich eine Inkonsistenz in meinem Programm.
Der Ausweg ist, dass jedes Objekt sich selbst serialisiert, d. h. ich rufe nur serialize() auf und jedes Objekt weiß von sich selbst am besten, wie es sich in einen Stream umzuformen hat.
-
Michael E. schrieb:
template<class Stream> virtual Stream& serialize(Stream& stream) = 0;
Ein Memberfunktionstemplate darf nicht virtuell sein.
-
wat wieso denn das?
Der typ ist doch festgelegt, da kann man doch keon falsches template aufrufen...
-
camper schrieb:
Michael E. schrieb:
template<class Stream> virtual Stream& serialize(Stream& stream) = 0;
Templatefunktionen können und dürfen nicht virtuell sein.Wieder den Standard auswendig gelernt?
:p
-
und grade wenn man viele hier Serializable hat, kann man alle auch wenn sie unterschiedlich sind in einem array (oder anderen iterierbaren Datentypen) unterbringen, und verwalten.
Bei grafischer programmierung gibt es auch eine Anwendungsmöglichkeit, denn da kann man beispielsweise alle grafisch darstellbaren Klassen (Helden, Steine, etc) mit einer Schnittstelle verwalten.
-
Michael E. schrieb:
Wieder den Standard auswendig gelernt?
:pDas sollte Grundwissen sein, wenn man mit solchen Konstrukten arbeitet - einerseits wird dich der Compiler sowieso drauf hinweisen, andererseits ist die Regel nicht wirklich überraschend.
-
@Michael E. ***einfacher gehts kaum?! Wenn ich mich dumm stell, also mal angenommen dann versteh das nicht mal ich.

Zur Frage:
Stell dir vor du schreibst ein Grafikprogramm mit einer Pluginschnittstelle, die andere Entwickler für ihre Plugins nutzen können.
Dann würdest du wahrscheinlich eine abstrakte Basisklasse, wie die folgende entwickeln:class IPlugin { public: virtual void ~IPlugin() = 0; virtual SUCCEEDED PluginTaskForImage(IMAGE **Image) = 0; .... };Der Pluginentwickler würde dann seine Pluginklasse von deiner "IPlugin" ableiten und hinterher bei deiner PluginAPI eine instanz dieser durch eine Funktion wie zB. "AddPlugin( IPlugin * Interface);" anmelden.
Das würde dann so aussehen:
class CPlugin : public IPlugin { public: CPlugin(...); void ~CPlugin(); SUCCEEDED PluginTaskForImage(IMAGE **Image); .... }; ... //zB. innerhalb der DllMain CPlugin *myPlugin = new CPlugin(...); PluginAPI::AddPlugin( static_cast<IPlugin*> myPlugin);MfG Wally
-
Danke für die vielen Antworten.
Michael E. schrieb:
Mit abstrakten Basisklassen kannst du Interfaces anbieten. In diesem Beispiel gebe ich Klassen, die von Serializable erben, eine Schnittstelle, mit deer ich sie serialisieren (z. B. in eine Datei speichern) kann.
Erstmal habe ich nicht ganz verstanden, was du mit Plugins meinst, aber durch die nachfolgenden Erklärungen ist es mir klar geworden.

Wally schrieb:
Der Pluginentwickler würde dann seine Pluginklasse von deiner "IPlugin" ableiten und hinterher bei deiner PluginAPI eine instanz dieser durch eine Funktion wie zB. "AddPlugin( IPlugin * Interface);" anmelden.
Jetzt hab ich es geschnallt. Der Grundgedanke ist also der, dass meine Plugin-API einen umkonvertierten Klassenzeiger der fremden (von der absktrakten Basisklasse abgeleiteten) Pluginklasse erhält, und dann die in der abgeleiteten Klasse redefinierten Methoden aufrufen kann, so als seien es die klasseneigenen Methoden, richtig?
-
Okay, das gibt mir die Bestätigung, es funktioniert:
#include <iostream> #include <string> class AbstractBasis { public: virtual void Test (const std::string &text) = 0; }; class DerivedClass : public AbstractBasis { public: void Test (const std::string &text) { std::cout << "Derived Class: " << text << std::endl; } }; class AbstractAPI { public: void AddPlugin (DerivedClass *Handler) { Handler->Test ("Hallo, ich bins"); } }; int main () { DerivedClass *derive = new DerivedClass; AbstractAPI api; api.AddPlugin (static_cast <DerivedClass*> (derive)); std::cin.get (); }Danke an euch!

-
Sorry, eine Unklarheit ist mir noch aufgekommen, es geht um folgendes:
Die Klasse AbstractAPI währe in diesem Beispiel eine Klasse meines Grafikprogramms, die eine Instanz der abgeleiteten (->fremden) Klasse erwartet, und dann dessen Methoden aufrufen kann. Genau das passiert in dieser Zeile:
void AddPlugin (DerivedClass *Handler) { Handler->Test ("Hallo, ich bins"); }Allerdings muss in den Funktionsparametern von AddPlugin () der Objekttyp der fremden Klasse bekannt sein (in dem Fall DerivedClass), um die übergebene Instanz einen bestimmten Klassentyp zuzuordnen zu können. In diesem Programmbeispiel klappt das ja noch ganz gut, da ich ja DerivedClass kenne, die aber eigentlich die (unbekannte) Fremdklasse des Plugins ist. Woher soll nun AddPlugin wissen, welchen Objekttyp *Handler zugeordnet werden soll? Folgendes Beispiel klappt so nicht:
void AddPlugin (void *Handler) { Handler->Test ("Hallo, ich bins"); }Welche Lösung gibt es da?
-
Und woher weißt du wie die Funktion heißt die du ausführst?
alsovoid AddPlugin (DerivedClass *Handler) {
Handler->Test ("Hallo, ich bins");
}Test(char * sonstwas) ??
-
Und woher weißt du wie die Funktion heißt die du ausführst?
Das ist ja nicht das Problem, er ruft lediglich die Funktionen auf, die er in seiner abstrakten Basisklasse vordefiniert hat, die anderen (ihm unbekannten) Funktionen der Klasseninstanz sind unrelevant. Nicht aber der Klassentyp

-
Das ist ja nicht das Problem, er ruft lediglich die Funktionen auf, die er in seiner abstrakten Basisklasse vordefiniert hat, die anderen (ihm unbekannten) Funktionen der Klasseninstanz sind unrelevant. Nicht aber der Klassentyp

Achso ich fürchte da kann ich nicht helfen würde mich aber auch interessieren

-
Genau dafür ist Polymorphie da. Du kannst folgendes schreiben:
void AddPlugin (AbstractBasis* Handler) { Handler->Test ("Hallo, ich bins"); }Dadurch, dass Handler ein Zeiger (ginge auch mit ner Referenz) ist, wird bei virtuellen Funktionsaufrufen erst mal der dynamische Typ des Objekts rausgefunden und dann die passende Funktion aufgerufen.
-
Dadurch, dass Handler ein Zeiger (ginge auch mit ner Referenz) ist, wird bei virtuellen Funktionsaufrufen erst mal der dynamische Typ des Objekts rausgefunden und dann die passende Funktion aufgerufen.
Mensch, jetzt wird's mir auch klar...
Hast ja Recht, manchmal sieht man den Wald vor lauter Bäumen nicht. Danke 