C++: Sinn und Zweck virtueller Methoden?

Moin,

ich habe eine Klasse und x Unterklassen.
Meine Basisklasse hat eine Methode y, die von allen x Unterklassen unterschiedlich überschrieben wird.
Wenn ich dann mit Objekten der Unterklassen die Methode y aufrufe, dann wird doch jedesmal die richtige Methode (die der jeweiligen Unterklasse zugehörige) aufgerufen, nicht?
Wozu brauche ich dann noch virtuelle Methoden?

thx
moe.

Servus,

ich habe eine Klasse und x Unterklassen.
Meine Basisklasse hat eine Methode y, die von allen x
Unterklassen unterschiedlich überschrieben wird.
Wenn ich dann mit Objekten der Unterklassen die Methode y
aufrufe, dann wird doch jedesmal die richtige Methode (die der
jeweiligen Unterklasse zugehörige) aufgerufen, nicht?

Richtig. Und es wird auch die richtige Methode aufgerufen, wenn du eine Referenz mit Typ der Basisklasse hast, und von dieser Referenz aus y aufrufst.

Wozu brauche ich dann noch virtuelle Methoden?

Damit das, was ich gerade beschrieben habe, funktioniert.
Also nochmal zusammengefasst:
Klasse A hat Methode y
Klasse B erbt von A, überschreibt y
Wenn du jetzt
A *foo = new B;
hast und foo->y aufrufst, muss y in A al virtuell deklariert sein, damit du die Variante aus B erhältst.

Verständlich?

Grüße,
Moritz

Servus,

ich habe eine Klasse und x Unterklassen.
Meine Basisklasse hat eine Methode y, die von allen x
Unterklassen unterschiedlich überschrieben wird.
Wenn ich dann mit Objekten der Unterklassen die Methode y
aufrufe, dann wird doch jedesmal die richtige Methode (die der
jeweiligen Unterklasse zugehörige) aufgerufen, nicht?

Richtig. Und es wird auch die richtige Methode aufgerufen,
wenn du eine Referenz mit Typ der Basisklasse hast, und von
dieser Referenz aus y aufrufst.

Wozu brauche ich dann noch virtuelle Methoden?

Damit das, was ich gerade beschrieben habe, funktioniert.
Also nochmal zusammengefasst:
Klasse A hat Methode y
Klasse B erbt von A, überschreibt y
Wenn du jetzt
A *foo = new B;
hast und foo->y aufrufst, muss y in A al virtuell
deklariert sein, damit du die Variante aus B erhältst.

Verständlich?

Naja. Warum verwende ich denn sowas wie

A *foo = new B;

und nicht einfach
A foo = new A
oder
B foo2 = new B
je nachdem, was ich haben will ?

thx
moe.

Naja. Warum verwende ich denn sowas wie

A *foo = new B;

und nicht einfach
A foo = new A
oder
B foo2 = new B
je nachdem, was ich haben will ?

Das absolute Lieblingsbeispiel vieler Autoren (mit th? WTF?) zu dem Thema:

class Form {
public:
 virtual void zeichnen( void );
};

class Kreis : Form {
public:
 void zeichnen( void ) { /\* zeichne einen Kreis \*/ }
};

class Dreieck : Form {
public:
 void zeichnen( void ) { /\* zeichne einen Kreis \*/ }
};

vector

Kram;

// …

void fuellen( void ) {
Kram.push_back( Kreis() );
Kram.push_back( Dreieck() );
Kram.push_back( Dreieck() );
}

void funktion( void ) {
for( vector::iterator it = Kram.begin();
it != Kram.end();
++it )
{
it->zeichnen();
}
}

(kann ein paar üble syntaktische Fehler enthalten, war eine ganze Weile nur in C unterwegs)

Naja. Warum verwende ich denn sowas wie

A *foo = new B;

und nicht einfach
A foo = new A
oder
B foo2 = new B
je nachdem, was ich haben will ?

thx
moe.

Hallo Moe,

weil du das nicht weisst oder nicht wissen willst. Klassisches Beispiel: ein generelles Graphisches Objekt, davon abgeleitet Kreis, Quadrat,… usw. Eine Seite Grafik ist eine Collection solcher Grafischer Objekte - muss sie neu gezeichnet werden, wird einfach die Paint-Methode aller Objekte aufgerufen, und dazu muss sie eben virtuell sein, sonst wird nämlich die Methode der Basisklasse aufgerufen (Weil der Compiler zur Compile Time ja nichts besseres weiss; virtuell wird dagegen erst zur Runtime bestimmt).

Natürlich könntest du das alles zu Fuss erledigen, z.B. indem du in jedes Objekt explizit hineinschreibst, ob es nun ein Kreis usw. ist, und das beim Paint abfragst, aber das könntest du ja mal vergessen oder falsch auswerten; und noch dazu müsstest du immer die gesamte Software überarbeiten, wenn ein neuer Typ wie Sechseck dazukommt - eine virtuelle Methode regelt das automatisch richtig. Dazu ist OO da, aber unbedingt gebraucht wird OO natürlich nicht, das hat schon Turing bewiesen ohne überhaupt was davon zu ahnen.

Gruss Reinhard

Hallo,

Nach den beiden ausführlicheren Beispielen
füge ich noch ein „Einfaches“ hinzu …

Wenn du jetzt
A *foo = new B;
hast und foo->y aufrufst, muss y in A al virtuell
deklariert sein, damit du die Variante aus B erhältst.

Naja. Warum verwende ich denn sowas wie

A *foo = new B;

und nicht einfach
A foo = new A
oder
B foo2 = new B
je nachdem, was ich haben will ?

Weil Du in einem übergeordneten Algorithmus
vom „konkreten Typ“ abstrahieren willst:

 ...
 RAKETE \* myArsenal[4];
 myArsenal[0] = new KORNET;
 myArsenal[1] = new ZELZAL2;
 myArsenal[2] = new FAJR3;
 myArsenal[3] = new FAJR5;

 if( Nasrallah.speaks() == ALLAHU\_AKBAR ) {
 k = rand % 4;
 myArsenal[k]-\>refuel();
 myArsenal[k]-\>target();
 myArsenal[k]-\>launch();
 }

vgl:
http://www.theonion.com/content/files/images/Prohibi…

Grüße

CMb

Hallo,

Nach den beiden ausführlicheren Beispielen
füge ich noch ein „Einfaches“ hinzu …

Wenn du jetzt
A *foo = new B;
hast und foo->y aufrufst, muss y in A al virtuell
deklariert sein, damit du die Variante aus B erhältst.

Naja. Warum verwende ich denn sowas wie

A *foo = new B;

und nicht einfach
A foo = new A
oder
B foo2 = new B
je nachdem, was ich haben will ?

Weil Du in einem übergeordneten Algorithmus
vom „konkreten Typ“ abstrahieren willst:


RAKETE * myArsenal[4];
myArsenal[0] = new KORNET;
myArsenal[1] = new ZELZAL2;
myArsenal[2] = new FAJR3;
myArsenal[3] = new FAJR5;

if( Nasrallah.speaks() == ALLAHU_AKBAR ) {
k = rand % 4;
myArsenal[k]->refuel();
myArsenal[k]->target();
myArsenal[k]->launch();
}

vgl:
http://www.theonion.com/content/files/images/Prohibi…

gibt es (abseits des etwas längeren Quelltextes) einen Grund, warum man das Problem nicht wie folgt lösen sollte? :

if( Nasrallah.speaks() == ALLAHU\_AKBAR )
{
 k = rand % 4;
 switch (k)
 {
 case 0:
 KORNET kornet = new KORNET;
 kornet.refuel();
 kornet.target();
 kornet.launch();
 case 1:
 ZELZAL2 zelzal2 = new ZELZAL;
 zelzal2.refuel();
 ..
 ....
 }
}

thx
moe.

gibt es (abseits des etwas längeren Quelltextes) einen Grund,
warum man das Problem nicht wie folgt lösen sollte? :

(w-w-w und Quelltext zitieren ist leider die Pest)

Ja, den gibt es. Abgesehen davon, dass du mal dringend die Speicherverwaltung von C++ durchgehen solltest, erstellst du jedesmal neue Objekte, anstatt vorhandene zu verwenden. Der Sinn von Objekten besteht aber gerade darin, dass sie einen Zustand haben, auf dem die Methoden operieren.

Hallo,

gibt es (abseits des etwas längeren Quelltextes) einen Grund,
warum man das Problem nicht wie folgt lösen sollte? :

if( Nasrallah.speaks() == ALLAHU_AKBAR )
{
k = rand % 4;
switch (k)
{
case 0:
KORNET kornet = new KORNET;
kornet.refuel();
kornet.target();
kornet.launch();
case 1:
ZELZAL2 zelzal2 = new ZELZAL;
zelzal2.refuel();


}

Ja, weil Du den Quelltext in meiner Variante
nicht mehr ändern musst, wenn Du mal neue
Raketen eingekauft hast.

Es würde immer noch funktionieren,
obwohl es die Raketen, die Du jetzt hast
damals (zur Implementation) gar nicht gab.

Die virtuellen Fuktionen stellen quasi ein
„Interface“ dar, welches durch jegliche
weitere hierarchische Spezialisierung
hindurch erhalten bleibt.

Grüße

CMБ

Hallo FlamingMoe,

Der Witz von Vererbung ist m.E. der:

Form \*foo = new Dreieck;

so bleibt es wahlfrei, entweder die Funktion der Klasse „Form“ aufzurufen, oder, wenn diese uebeschrieben ist, dann eben die von „Dreieck“.

Wuerdest Du deklarieren:

Dreieck \*foo = new Dreieck;

koenntest Du die (nicht ueberschriebenen) Funktionen der Basisklasse nicht verwenden, und koenntest Dir gleich die ganze Ableiterei sparen.
Mit anderen Worten: Ein Basiszeiger (vom Typ „Form“) muss her, weil nur der auf beide Klassen zugreifen kann.

haste das?

lg
Martin

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Moin,

ich habe eine Klasse und x Unterklassen.
Meine Basisklasse hat eine Methode y, die von allen x
Unterklassen unterschiedlich überschrieben wird.
Wenn ich dann mit Objekten der Unterklassen die Methode y
aufrufe, dann wird doch jedesmal die richtige Methode (die der
jeweiligen Unterklasse zugehörige) aufgerufen, nicht?
Wozu brauche ich dann noch virtuelle Methoden?

thx
moe.

Hallo FlamingMoe,

Der Witz von Vererbung ist m.E. der:

Form *foo = new Dreieck;

so bleibt es wahlfrei, entweder die Funktion der Klasse „Form“
aufzurufen, oder, wenn diese uebeschrieben ist, dann eben die
von „Dreieck“.

Wuerdest Du deklarieren:

Dreieck *foo = new Dreieck;

koenntest Du die (nicht ueberschriebenen) Funktionen der
Basisklasse nicht verwenden, und koenntest Dir gleich die
ganze Ableiterei sparen.
Mit anderen Worten: Ein Basiszeiger (vom Typ „Form“) muss her,
weil nur der auf beide Klassen zugreifen kann.

haste das?

noch nicht ganz:

über

class Dreieck: public Form {...};
Dreieck \*foo = new Dreieck;
Dreieck.Form.fuktion();

kann ich doch trotzdem auf die Funktionen der Basisklasse zugreifen?! Vorausgesetzt die Basisfunktion ist public oder protected… , oder?

thx
moe.

Du sprichst in Rätseln. Bist Du Unternehmensberater ?? :wink: Was ist mit dem Speicher ?

thx
moe.

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hallo

über

class Dreieck: public Form {…};
Dreieck *foo = new Dreieck;
Dreieck.Form.fuktion();

kann ich doch trotzdem auf die Funktionen der Basisklasse
zugreifen?! Vorausgesetzt die Basisfunktion ist public oder
protected… , oder?

Nein nicht.

Da hast Du imho zwei Fehlkonzeptionen drin.

  1. Du kannst generell nicht aus einer Basisklasse auf
    die Methoden der abgeleiteten Klasse „durchgreifen“,
    sofern die Methode nicht „virtual“ ist.

  2. die Syntax „Dreieck.Form.fuktion()“ weist darauf
    hin, dass „Form“ ein Compound-Objekt innerhalb von „Dreieck“ ist,
    etwa

    class FORM {
    public:
    void funktion(void);
    };

    class Dreieck {
    public:
    FORM Form;
    };

    Dreieck *foo = new Dreieck;
    Dreieck->Form.fuktion();

Der Sinn bei der virtuellen Vererbung soll
ja sein, dass Du einen Kontäner hast, der
sich um die konkreten Typen nicht kümmert,
erst wenn es darauf ankommt, weiss das
dahinter verborgene Objekt selbst
wie es mit seinen Daten umzugehen hat.

Die umgebenden Algorithmen müssen das
nicht wissen - das ist der Sinn.

Grüße

CMБ

Wuerdest Du deklarieren:

Dreieck *foo = new Dreieck;

koenntest Du die (nicht ueberschriebenen) Funktionen der
Basisklasse nicht verwenden, und koenntest Dir gleich die
ganze Ableiterei sparen.
Mit anderen Worten: Ein Basiszeiger (vom Typ „Form“) muss her,
weil nur der auf beide Klassen zugreifen kann.

haste das?

noch nicht ganz:

über

class Dreieck: public Form {…};
Dreieck *foo = new Dreieck;
Dreieck.Form.fuktion();

kann ich doch trotzdem auf die Funktionen der Basisklasse
zugreifen?! Vorausgesetzt die Basisfunktion ist public oder
protected… , oder?

thx
moe.

hallo FlamingMoe,

die Funktion muss schon protected (public) sein, aber Du brauchst den Basis-Zeiger in Verbindung mit der virtual-Deklaration.
Das „virtual“ ist notwendig nur in der Basis-Klasse; es ist aber allemal anzuraten auch in der abgeleiteten Klasse, so kannst Du auch von dieser wieder ableiten.

Aber wenn Du Dir so sicher bist, dass Deine Methode besser geht, dann probier es doch einfach aus (beide Varianten). Ich nehme aber mal an, sie tut es nicht, sonst wuerdest Du ja hier nicht anfragen… :wink:

lg
Martin

kann ich doch trotzdem auf die Funktionen der Basisklasse
zugreifen?! Vorausgesetzt die Basisfunktion ist public oder
protected… , oder?

thx
moe.

Hallo Moe,

du denkst in eine falsche Richtung - es geht bei diesem Problem überhaupt nicht darum, ob man etwas nicht auch selbst erledigen könnte, sondern um die Frage, ob der Compiler das nicht besser und sicherer kann. Der wählt immer die richtige virtuelle Methode aus, ob du dagegen absolut fehlerfrei programmierst, ist eher zu bezweifeln.

Grundsätzlich kann man alles auch ohne Unterstützung erledigen - bei meinen ersten Programmen habe ich die relativen Sprungdistanzen noch per Hand im Hexadezimalcode abgezählt. Es geht, aber sinnvoll ist es nicht, was glaubst du, wie oft ich mich verzählt habe. Die erst später verfügbaren Assembler haben sich NIE geirrt.

Gruss Reinhard

Danke .
.