Abstrakte Klasse
-
Hallo Forum,
nehmen wir an ich möchte einer Methode ein Objekt entweder vom Typ Fahrrad oder Auto übergeben. Dann könnte ich doch die Basisklassen Fahrzeug erfinden, davon Auto und Fahrrad ableiten. Der Methode würde ich dann ein Fahrzeug übergeben. Mein Problem: Wie kriege ich es hin das nur Auto oder Fahrrad übergeben wird -> Es darf auf keinen Fall ein Fahrzeug übergeben werden. Im Internet steht ich muß in Fahrzeug eine Mathode als Pure Virtual definieren. Mir fällt aber keine Methode in Fahrzeug ein die von Auto/Fahrrad überschrieben werden. Ich könnte jetzt eine besthende Methode in Fahrzeug pure virtual machen und dann aus Fahrzeug den Methodenkörper nach Fahrrad/Auto den kopieren... wäre aber ein workaround.
Gibt es vielleicht ein einfachere Methode um eine Klasse in C++ abstract zu machen?
Vielen Dank
Martin
-
nimm doch als methode:
double getMaxVelocity()=0;oder
Fahrzeug* clone()=0;oder
std::string getName()=0;und wenn du ansonsten keine gemeinsame methode kennst, die beide abgeleiteten klassen benötigen, dann ist das klassendesgin ohnehin fragwürdig
-
Davon abgesehen, dass die Übergabe einer Basisklasse absolut keinen Sinn macht, wenn sie nicht das Interface der abgeleiteten Klassen bereitstellt (wie willst Du sonst an die Daten kommen? dynamic_cast und typeid? Schlechtes Design!), kannst Du jederzeit den Destruktor abstrakt machen. Da jede Klasse (auch implizit) einen Destruktor besitzt und da polymorphe Klassen immer auch einen virtuellen Destruktor haben sollten, vergrößert sich dadurch auch nicht die Klasse.
-
Wenn die Klassen so unterschiedlich sind, daß sie noch nichtmal gemeinsame Methoden haben, lohnt sich eine Ableitungsbeziehung nicht wirklich (vor allem weil du dann in deiner Funktion ständig nachsehen müsstest, was für ein Typ wirklich vorliegt). Wenn die Methoden bei beidem identisch behandelt werden können, wozu hast du das dann in zwei Klassen aufgeteilt?
Im allgemeinen Fall sollte das Design etwa so aussehen:
class fahrzeug { public: virtual void fahre(...) = 0 ;// das Fahrzeug kann fahren - aber erst konkrete Klassen wissen, wie virtual ~fahrzeug();//Dtor ... }; class fahrrad : public fahrzeug { virtual void fahre(...) { steig_auf(); ...tret-tret-keuch-keuch... } ... }; class auto : public fahrzeug { virtual void fahre(...) { starte_motor(); gibt_gas(); ... } ... };(und schon hast du etwas, was du pur virtuell anlegen kannst

PS: Und wenn alle Stricke reißen, kannst du immer noch einen pur virtuellen Destruktor definieren.
-
Hi. Also zuerst einmal bietet es sich an, bzw. draengt sich auf, bei einer Basisklasse in einer hierarchie immer den Destruktor virtual zu deklarieren. Der Grund ist, dass wenn du beispiels weise delete auf einen Zeiger der Basisklasse anwendest, schliesslich das gesamte Objekt zerstoert werden soll, und nicht nur der Basis Teil. Und wenn du den Destruktor schon virtuell hast, dann kannst du ihn auch gleich pure virtual machen.
Und einen Funktionsrumpf zu kopieren ist verdoppeln von Informationen, was bei Veraenderungen stark fehleranfaellig ist. Sinvoller ist es, in den abgeleiteten Funktionen die geerbte aufzurufen. Zusammen sieht das dann so aus:
class Fahrzeug { public: /*pure*/ virtual ~Fahrzeug() = 0 {}; /*pure*/ virtual void Fahre() = 0 { cout << "brrrrmmm...." << endl; } } class Auto : public Fahrzeug { ~Auto() {}; //virtual kann man weglassen, wenns in der Elternklasse deklariert wurde... virtual void Fahre() //muss man aber nicht { Fahrzeug::Fahre(); } //geerbte Methode aufrufen }Kleine Merkregel fuer Klassenhierarchien die im Zusammenhang mit Polymorphie verwendet werden sollen:
normaler Destruktor: man sollte nicht ableiten.
virtual Destruktor: man darf ableiten.
pure virtual Destruktor: man MUSS ableiten.
-
@pumuckl: Diese Kombination aus pur virtuell UND Definition sieht zwar recht nett aus, ist aber leider im Ansi Standard nicht erlaubt. Du kannst gerne beides angeben, aber dann mußt du die Methodendefinition hinter der Klasse bringen.
-
Das mit dem Pure Destructor würde klappen vielen Dank

Zu dem Design: Da könntet Ihr recht haben. Denn meine nächste Frage wäre wie ich in der Methode die ein Fahrzeug bekommt herausbekommen würde was übergeben worden ist.
Methoden der Klasse Fahrzeug:
SetSpeed()
GetSpeed()
SetPrice()
GetPrice()
...Methoden der Klasse Fahrrad:
SetLenkradGepäckträger()
GetLenkradGepäckträger()Methoden der Klasse Auto:
SetAnhängerkupplung()
GetAnhängerkupplung()In den Methoden der Klasse Fahrzeug steckt eine Menge Code zur Überprüfung drin. Deshalb soll dieser nur einmal auftreten. In meiner Hauptapplikation muß aber ein komplettes Fahrzeug da sein. Fahrrad oder Auto. Wie würde ich nun herausbekommen was mir übergeben wurde?
-
Zur Not kann man auch den Konstruktor protected machen.
-
entschuldige, war eine Schludrigkeit meinerseits, um das Ganze kuerzer zu gestalten.
-
Also da ist dein Desing tatsächlich kaputt - entweder du schreibst dir Überladungen für alle Fahrzeug-Typen, die dann auf den jeweiligen Typ spezialisiert sind, oder du verwedest d*** (ich empfehle ersteres - oder eine Überarbeitung des Designs).
-
martin_salo schrieb:
In den Methoden der Klasse Fahrzeug steckt eine Menge Code zur Überprüfung drin. Deshalb soll dieser nur einmal auftreten. In meiner Hauptapplikation muß aber ein komplettes Fahrzeug da sein. Fahrrad oder Auto. Wie würde ich nun herausbekommen was mir übergeben wurde?
Ohne typecasts oder typeid (die meist unschoen sind) garnicht. Normalerweise solltest du auch nicht die Typinformation verlieren, wenn du wissen musst, womit du nun genau arbeitest. Lediglich Funktionen, denen egal ist, ob sie nun mit einem Fahrrad oder einem Auto arbeiten, koennen einfach ein Fahrzeug akzeptieren.
void RenoviereAnhaengerkupplung(Auto& a) { // Es waere sinnlos, das bei einem Fahrzeug zu machen, weil nur Autos sowas haben } void KLemmeAufGepaecktraeger(Fahrrad& f, Gepaeckstueck& g) { // geht bei Fahrzeugen allgemein nicht, nur bei Fahrraedern } void Verkaufe(Fahrzeug& f) { f.SetPrice(); //geht fuer alle fahrzeuge... }Nur wenn du beispielsweise einen Haufen fahrzeuge in einen Container geworfen hast und sie hinterher auseinanderosrtieren musst, ist es wichtig, den Typ wieder rauszufinden.
-
Kommando zurück. Ich habs:
Ich mache zwei Methoden in meinem Hauptobjekt:
Anstatt:
SetFortbewegungsmittel(Fahrzeug &Obj);
Mache ich:
SetFortbewegungsmittel(Auto &Obj);
SetFortbewegungsmittel(Fahrrad &Obj);Und Auto/Fahrrad leite ich wie bisher von Fahrzeug ab. In SetFortbewegungsmittel() kann ich dann kein Fahrzeug mehr übergeben...
-
Kannst du so oder so nicht, da Fahrzeug dank pure virtual Methoden eine abstrakte Klasse ist. Sprich: zur laufzeit deines Programms gibt es nur Autos, Fahrraeder, meinetwegen auch Trecker oder Zuege, aber keine reinen Fahrzeuge. Und falls du auch noch spaeter ein Motorrad zufuegen moechtest, muesstest du noch eine zusaetzliche Methode SetFortbewegungsmittel(Motorrad& m) definieren usw. Bis dahin hast du also mit der KLassenhierarchie garnichts gewonnen. Zeig doch mal, was du bisher hast, und erklaer uns in groben Zuegen, was du damit erreichen willst.
-
In Fahrzeug sind sehr viele Methoden mit Code. Es sind Getter und Setter mit Überprüfung der Daten. Der Dekonstruktor ist pure virtual.
Methoden der Klasse Fahrzeug:class Fahrzeug { SetSpeed() {// Mit Code} GetSpeed() {// Mit Code} SetPrice() {// Mit Code} GetPrice() {// Mit Code} ... virtual ~Fahrzeug()=0; // Damit Fahrzeug nicht instanziiert werden kann. } // Rumpf zum Dekonstruktor: Fahrzeug:~Fahrzeug() { // Dieser Dekonstruktor wird aufgerufen bei der Zerstörung eines Autos/Fahrrads. delete obj1; delete obj2; ... }Die Klassen Fahrrad und Auto haben alle Getter und Setter von Fahrzeug und zusätzlich ein paar Extra Methoden:
Beispielsweise Methoden der Klasse Fahrrad:
class Fahrrad : public Fahrzeug { SetLenkradGepäckträger() {// Mit Code} GetLenkradGepäckträger() {// Mit Code} ~Fahrzeug() { :~Fahrzeug(); // Objekte der Basisklasse zerstören. delete abc; } }In meiner Hauptklasse habe ich dann zwei Methoden:
SetFortbewegungsmittel(Fahrrad &F) { } SetFortbewegungsmittel(Auto &A) { }Ich muß eh beide Objekte unterschiedlich behandeln. Wenn jetzt noch ein Motorrad hinzukommt hat es auch extra Daten Getter/Setter, so dass ich in meiner Hauptklasse sowieso eine neue Methode machen muß.
-
martin_salo schrieb:
~Fahrzeug() { //sollte wohl Fahhrad heissen :~Fahrzeug(); // Objekte der Basisklasse zerstören. delete abc; }Den Basisklassen Destruktor brauchst du meines Wissens nicht aufzurufen, da die Basisklassen bestandteile immer zerstoert werden, und zwar nach Ausfuehrung des abgeleiteten Destruktors. damit wird auch sichergestellt, dass etwaige abhaengige Aktionen nicht auf ein schon zerstoertes Objekt zugreifen.
Bisher erkenne ich allerdings nicht, wie du in deiner Hauptklasse mit den Fahrzeugen umgehen willst. Wenn du schon bei SetFortbewegungsmittel zwischen den verschiedenen Typen unterscheidest, musst du das vermutlich auch bei der Verwendung in der Hauptklasse, und das widerum klingt nach unmengen von if-else kaskaden bzw. switch-cases
-
~Fahrzeug() { :~Fahrzeug(); // Objekte der Basisklasse zerstören. delete abc; }funzt das übehaupt? ist das std-konform? kommt es da zur laufzeit nicht zu einem bösen absturz??
und überhaupt was soll das bringen???allg.:
class Base{ public: Base() {std::cout<<"Base"<<std::endl; } virtual ~Base(){ std::cout<<"~Base"<<std::endl;} }; class D1 : public Base { D1 () {std::cout<<"D1 "<<std::endl; } ~D1(){ std::cout<<"~D1 "<<std::endl;} }; class D2 : public D1 { D2 () {std::cout<<"D2 "<<std::endl; } ~D2(){ std::cout<<"~D2 "<<std::endl;} }; int main() { D2 d1; std::cout<<std::endl; }--> output
Base
D1
D2~D2
~D1
~Base
-
Zuerst war nur Auto da. Mein Aufgabe war das Programm auf Fahrrad zu erweitern. Das ist mir jetzt gelungen, ohne das ich den Code duplizieren musste (doppelter code in Fahrzeug ausgelagert). Mit den if Statements hast Du recht. Ich glaube aber nicht das ich da herum komme. Das Programm muß sich bei einem Fahrrad etwas anders verhalten.
Danke für den Tip mit dem Dekonstruktor.
-
Da solltest du versuchen, dieses unterschiedliche Verhalten IN den Klassen zu verpacken - in Form von virtuellen Methoden (siehe mein Beispiel oben - sowohl auto als auch fahrrad haben eine Methode fahre(), aber jede Klasse hat eine andere Art, wie sie fahren will).
-
@muffmolch:
Ich wollte sicherstellen das der Dekonstruktor aufgerufen wird. Wenn er zweimal aufgerufen wird wird ein Objekt zweimal deleted. Ich habe den Aufruf aber mitlerweile entfernt.
-
Sei nicht schuechtern, zeig doch einfach dein Programm und was es macht (evtl gekuerzt aufs wesentliche)
martin_salo schrieb:
@muffmolch:
Ich wollte sicherstellen das der Dekonstruktor aufgerufen wird. Wenn er zweimal aufgerufen wird wird ein Objekt zweimal deleted.Wenn du Glueck hast passiert nicht viel, aber wenn du im geerbten Destructor z.B. deletes stehen hast, wird beim zweiten mal delete aufgerufen mit einem Argument, das irgendwo aus dem bereits "zerstoerten" gelesen wird.
And we enter the dark shady realms of undefined behavior.
-
@CStoll: Meine Klassen stellen nur Datenobjekte dar die gefüllt werden. Sie haben nur ein Methode IsValid() die sagt ob das Klassenobjekt mit einem konsitentem Datensatz gefüllt wurde. Alle Datenklasse werden dann einer Klasse zur Verarbeitung gegeben. Würde ich alle Datenklassen mit der Verarbeitungsklasse zusammenlegen hätte ich eine Menge (für mich zu viele) Methoden.
Es widerspricht vielleicht etwas dem OO Ansatz Daten und Funktionen zusammenzufügen, für mich ist es aber schön übersichtlich.
class Fortbewegung { SetFahrzeug(Auto A); SetFahrzeug(Fahrrad F); AddInsasse(Person P); DoFortbewegung() throw (UngueltigeAngaben); IsValid() throw (UngueltigeAngaben); }Den Code kann ich nicht zeigen. Nur soviel es geht um ein Dateiformat in XML wo unterschiedliche Objekte abgespeichert werden. class Fortbewegung ist in Wirklichkeit class DateiformatXYZ.