Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Polymorphismus und Rückgabewerte

2 views
Skip to first unread message

David Pritzkau

unread,
Jun 29, 2009, 2:58:02 PM6/29/09
to
Hallo!

Ich habe zwei Klassen- die eine hat von der anderen geerbt. Jetzt suche
ich nach einer Mᅵglichkeit eine Funktion zu programmieren, die als
Rᅵckgabewert die Elternklasse besitzt, jedoch so, dass ich diese aus dem
Aufrufenden Code mit einem Typecast in die andere umwandeln kann, wenn
es sich denn um diese handelt. Hierzu ein kleiner Pseudocode zum
Verstᅵndnis:

class A {};
class B : public A {};

A' myFunction() { // Pseudocode - geht so natᅵrlich nicht
if (today == Sonnig)
return A(); // auch das geht so natᅵrlich nicht
else
return B(); // soll veranschaulichen, dass mal A
// und mal B zurᅵckgegeben wird
}

// irgendwo im Programm:
A* a = myFunction();
cout << a->someMember();
B* b = dynamic_cast<B*>a;
if (b)
cout << b->someOtherMember();

Vielleicht habe ich ein Brett vorm Kopf - wie macht man sowas?

Grᅵ

Thomas J. Gritzan

unread,
Jun 29, 2009, 5:59:51 PM6/29/09
to
David Pritzkau schrieb:

Du musst einen Zeiger zurᅵckgeben, anders geht es nicht. Am Besten einen
schlauen Zeiger (smart pointer), denn:
1) der richtige smart pointer dokumentiert das Besitzverhᅵltnis, d.h.
wer sich um das "delete" kᅵmmern muss.
2) ein smart pointer rᅵumt automatisch auf; Speicherlecks werden somit
verhindert/vermindert.

class A
{
public:
/* Der Destruktor muss virtuell sein,
* damit das spᅵtere "delete" auf einen A* Zeiger funktioniert,
*/ auch wenn der Zeiger auf ein Objekt der Klasse B zeigt.
virtual ~A() {}
};
class B : public A {};

std::auto_ptr<A> myFunction() {
if (1 == 0)
return std::auto_ptr<A>(new A);
else
return std::auto_ptr<A>(new B);
}

Type-casts solltest du ᅵbrigens vermeiden, wenn es geht. Es ist
schlechter Stil, den Typ einer Variable mit dynamic_cast abzufragen,
wenn es anders und eleganter geht, zum Beispiel mit virtuellen
Funktionen. So sparst du dir beim Implementieren einer weiteren
Unterklasse, alle Funktionen mit Type-casts zu durchsuchen und den Cast
fᅵr die neue Klasse hinzuzufᅵgen.

--
Thomas

James Kanze

unread,
Jun 30, 2009, 3:27:49 AM6/30/09
to
On Jun 29, 8:58 pm, David Pritzkau <pseudotetrade...@web.de> wrote:

> Ich habe zwei Klassen- die eine hat von der anderen geerbt.

> Jetzt suche ich nach einer Möglichkeit eine Funktion zu
> programmieren, die als Rückgabewert die Elternklasse besitzt,


> jedoch so, dass ich diese aus dem Aufrufenden Code mit einem
> Typecast in die andere umwandeln kann, wenn es sich denn um

> diese handelt. Hierzu ein kleiner Pseudocode zum Verständnis:

> class A {};
> class B : public A {};

> A' myFunction() { // Pseudocode - geht so natürlich nicht
> if (today == Sonnig)
> return A(); // auch das geht so natürlich nicht


> else
> return B(); // soll veranschaulichen, dass mal A

> // und mal B zurückgegeben wird
> }

> // irgendwo im Programm:
> A* a = myFunction();
> cout << a->someMember();
> B* b = dynamic_cast<B*>a;
> if (b)
> cout << b->someOtherMember();

> Vielleicht habe ich ein Brett vorm Kopf - wie macht man sowas?

Nicht mit einer Wertsemantik. Erbschaft und Wertsemantik passen
schlecht zusammen. (Gewöhnlicherweise sperrt man die Zuweisung,
und oft sogar die Kopie in einer Basisklasse.) In deinem Fall
heißt es, Rückgabe per Zeiger und dynamische Allokierung. Anders
geht's nicht.

--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Daniel Krügler

unread,
Jun 30, 2009, 2:03:53 AM6/30/09
to
On 29 Jun., 20:58, David Pritzkau <pseudotetrade...@web.de> wrote:
> Ich habe zwei Klassen- die eine hat von der anderen geerbt. Jetzt suche
> ich nach einer Möglichkeit eine Funktion zu programmieren, die als
> Rückgabewert die Elternklasse besitzt, jedoch so, dass ich diese aus dem

> Aufrufenden Code mit einem Typecast in die andere umwandeln kann, wenn
> es sich denn um diese handelt. Hierzu ein kleiner Pseudocode zum
> Verständnis:

>
> class A {};
> class B : public A {};
>
> A' myFunction() { // Pseudocode - geht so natürlich nicht
> if (today == Sonnig)
> return A(); // auch das geht so natürlich nicht

> else
> return B(); // soll veranschaulichen, dass mal A
> // und mal B zurückgegeben wird

>
> }
>
> // irgendwo im Programm:
> A* a = myFunction();
> cout << a->someMember();
> B* b = dynamic_cast<B*>a;
> if (b)
> cout << b->someOtherMember();
>
> Vielleicht habe ich ein Brett vorm Kopf - wie macht man sowas?

Deine Benutzungscode weist dir schon die Richtung.

Deine Funktion "myFunction" muss als Rückgabetyp
einen Zeiger (oder eine Referenz) auf die Basisklasse
A haben, also die Signatur

A* myFunction();

oder

A& myFunction();

aufweisen. Als weitere Voraussetzung für einen erfolgreichen
(und wohlgeformten) dynamic_cast muss die Basisklasse
polymorph sein, also mindestens eine virtuelle Funktion aufweisen,
klassischerweise einen virtuellen Destruktor, damit auch
delete (falls verwendet) dynamisch korrekt arbeitet, z.B.:

class A { public: virtual ~A(){} };

Ich bin nicht ganz sicher, ob ich nun deine Frage ausreichend
beantwortet habe, oder ob das eigentliche Problem der Code
innerhalb von myFunction ist. Wenn letzteres noch unklar ist,
wie wäre es hiermit:

A* getA() { ... }
B* getB() { ... }

A* myFunction() {
if (today == Sonnig)
return getA();
else
return getB();
}

Wichtig ist: getA() und getB() sollten eine eindeutige
und am besten gleichartige Resourcen-
Verantwortlichkeit bezüglich ihrer Rückgabewerte
haben. Muss der Aufrufer delete aufrufen? Oder ist
das jeweilige Objekt nicht zu löschen, sondern
befindet sich z.B. im statischen Speicherbereich?

Die Verwendung eines entsprechenden Smartpointers
(std::auto_ptr, boost::shared_ptr) kann hier sehr
hilfreich sein.

Besten Gruss aus Bremen,

Daniel Krügler

Thomas Thiele

unread,
Jun 30, 2009, 4:46:26 AM6/30/09
to
On 29 Jun., 23:59, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:

> Type-casts solltest du übrigens vermeiden, wenn es geht. Es ist


> schlechter Stil, den Typ einer Variable mit dynamic_cast abzufragen,
> wenn es anders und eleganter geht, zum Beispiel mit virtuellen
> Funktionen.

Er hat aber explizit für B someOtherMember(); aufgerufen und
diese Funktion muss es in A nicht geben und dort auch nicht
sinnvoll sein.
Und dieses "ah...ich habe hier noch was spezielleres" lässt
sich IMHO nur mit dynamic_cast erreichen.
Ich finde es keinen schlechten Stil und finde auch nicht, dass
es *unbedingt* vermieden werden sollte. Polymorphie funktioniert
nur da wo abgeleitete Klassen etwas *anders* machen. Aber
nicht wo sie etwas *zusätzlich* machen.

Thomas J. Gritzan

unread,
Jun 30, 2009, 9:19:25 AM6/30/09
to
Thomas Thiele schrieb:

> On 29 Jun., 23:59, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:
>
>> Type-casts solltest du �brigens vermeiden, wenn es geht. Es ist

>> schlechter Stil, den Typ einer Variable mit dynamic_cast abzufragen,
>> wenn es anders und eleganter geht, zum Beispiel mit virtuellen
>> Funktionen.
>
> Er hat aber explizit f�r B someOtherMember(); aufgerufen und

> diese Funktion muss es in A nicht geben und dort auch nicht
> sinnvoll sein.
> Und dieses "ah...ich habe hier noch was spezielleres" l�sst

> sich IMHO nur mit dynamic_cast erreichen.
> Ich finde es keinen schlechten Stil und finde auch nicht, dass
> es *unbedingt* vermieden werden sollte. Polymorphie funktioniert
> nur da wo abgeleitete Klassen etwas *anders* machen. Aber
> nicht wo sie etwas *zus�tzlich* machen.

Du hast recht, es ist nicht immer schlechter Stil.

Wenn es nur eine oder wenige Abfragen sind, z.B. ob ein Objekt ein
bestimmtes "interface" unterst�tzt, also von einer bestimmten
Basisklasse abgeleitet ist, um dessen speziellere Funktionen zu nutzen,
kann man das durchaus ohne schlechtes Gewissen machen.

Sobald man aber eine l�ngere if/else-Kette hat, sollte man �berlegen, ob
es nicht auch anders geht:

if (B* b = dynamic_cast<B*>(ptr))
/* ... */
else if (C* c = dynamic_cast<C*>(ptr))
/* ... */
else if (D* d = dynamic_cast<D*>(ptr))
/* ... */
else if (E* e = dynamic_cast<E*>(ptr))
/* ... */
else if (F* f = dynamic_cast<F*>(ptr))
/* ... */

Das ist etwa so schlecht wartbar wie ein gr��erer switch/case-Block.

--
Thomas

Thomas Thiele

unread,
Jun 30, 2009, 11:45:02 AM6/30/09
to
On 30 Jun., 15:19, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:

> Sobald man aber eine längere if/else-Kette hat, sollte man überlegen, ob


> es nicht auch anders geht:

> Das ist etwa so schlecht wartbar wie ein größerer switch/case-Block.

Aber welche Lösung gibt es denn:

class Mensch{}

class Musican : public Mensch {
}

class Bassist : public Musican {
public:
void playBass(const Bass& b);
}

class Drummer : public Musican {
public:
void playDrumms(Sticks& s, Drumset& d);
}

class Trumpeter: public Musican {
public:
void blow(const Trumpet& s);
}

class Groupie : public Mensch{
public:
void playWitchMusician(Musican & m);
}

class Innkeeper: public Mensch{
public:
void fill(Tankard& m);
}

usw...noch viele Klassen mehr.

Und wenn man jetzt ein Mensch-Objekt bekommt muss man
geg. einzeln durchgehen, was man hat, und ihn entsprechend anweisen.
Es macht vielleicht noch einigermassen Sinn, wenn auch Groupie
noch blow() implementiert. Aber alle möglichen Funktionalitäten
von allen geg. leer zu implementieren ist noch schlechterer Stil.
Da dann selbst die Basis verändert werden muss, wenn eine neue
Klasse hinzukommt oder verändert wird.
Und eine allgemeine Funktion ala playYourInstrument() oder
doWhatEverYouCan() geht auch nicht, weil die Aufrufkonvention
jedesmal eine andere ist.
Praktisches Beispiel: Behandlung von unterscheidlichen Lichtquellen
(Infinite, Sphere, Projector) in Raytracing-Programm.
Infinite hat keine Position, Sphere keine Richtung, Projector beides.
Was tun?
Ich habe dafür keine Lösung um switch zu vermeinden.

Und wenn du mal Win-API-event-Behandlungs-callback-Funktionen
geschrieben hast...dann schockt einem kein großer switch-block mehr...

Thomas J. Gritzan

unread,
Jun 30, 2009, 2:04:31 PM6/30/09
to
Thomas Thiele schrieb:

> On 30 Jun., 15:19, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:
>
>> Sobald man aber eine l�ngere if/else-Kette hat, sollte man �berlegen, ob

>> es nicht auch anders geht:
>> Das ist etwa so schlecht wartbar wie ein gr��erer switch/case-Block.

>
> Praktisches Beispiel: Behandlung von unterscheidlichen Lichtquellen
> (Infinite, Sphere, Projector) in Raytracing-Programm.
> Infinite hat keine Position, Sphere keine Richtung, Projector beides.
> Was tun?
> Ich habe daf�r keine L�sung um switch zu vermeinden.

Wozu brauchst du zum Rendern ein Interface f�r Position und Richtung?

In meinem Uni-Praktikum haben wir ein "Raytracer Baukasten" bekommen,
mit dem fertigen Klassenmodell, aber ohne Implementation. Die
Basisklasse CLight hat nur die Funktion
virtual void ShadowFeeler( /* ... */ );
und die speziellen Klassen implementieren diese Funktion und bieten
Setter f�r die Eigenschaften der Lichtquelle an. Damit k�mmert sich die
Lichtquellen-Implementation um ihre Funktionalit�t.

> Und wenn du mal Win-API-event-Behandlungs-callback-Funktionen

> geschrieben hast...dann schockt einem kein gro�er switch-block mehr...

Urgs... ein gro�er switch-Block, ich kenne das. Besonders �bersichtlich
oder wartbar ist es aber nicht. Zum Gl�ck gibt es ja GUI Frameworks, die
das ganze in Klassen kapseln und die Ereignisse in Callback-Aufrufe
umsetzen.

--
Thomas

Thomas Thiele

unread,
Jul 1, 2009, 5:28:46 AM7/1/09
to
On 30 Jun., 20:04, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:

> > Praktisches Beispiel: Behandlung von unterscheidlichen Lichtquellen
> > (Infinite, Sphere, Projector) in Raytracing-Programm.
> > Infinite hat keine Position, Sphere keine Richtung, Projector beides.
> > Was tun?

> > Ich habe dafür keine Lösung um switch zu vermeinden.

> Wozu brauchst du zum Rendern ein Interface für Position und Richtung?

Verstehe die Frage nicht.
Es ist nunmal so, dass die drei Lichtquellentypen (die ich benutzt)
hatte,
völlig unterschiedlich behandelt werden mussten. Auch mathematisch.
Und die Lichtquellen selber sollten nichts von eigentlichen rendern
wissen und mitbekommen. Simple dumme Lichtquellen...
Und diese Lichtquellen konnten wie alle Objekte im Raum verschoben
und gedreht werden, waren also auch von der Basis Object abgeleitet.
Wie gesagt, ich habs letztendlich durch eine große switch anweisung
gelöst. Für andere Ideen bin ich offen.
Ich müsste das Projekt mal aufbohren/weitermachen.
So ein abgespekt (war mal vieil ausführlicher) ist es ein nützlicher
kleiner renderer für Grafik in Dialogen etc.

> und die speziellen Klassen implementieren diese Funktion und bieten

> Setter für die Eigenschaften der Lichtquelle an. Damit kümmert sich die
> Lichtquellen-Implementation um ihre Funktionalität.

Finde ich vom Design nicht gut!
Das ist OOP mit aller Gewalt. Das Gegenteil von dependency injection.
Beide Extremfälle finde ich unglücklich.
Im ersten Fall weiss die Klasse zuviel (sie soll *nur* Licht machen
und
nicht berechnen wie das Licht auf andere Körper trifft, dort
reflektiert
wird und auf die Kamera trifft.) im zweiten viel zu wenig.

> Urgs... ein großer switch-Block, ich kenne das. Besonders übersichtlich
> oder wartbar ist es aber nicht. Zum Glück gibt es ja GUI Frameworks, die


> das ganze in Klassen kapseln und die Ereignisse in Callback-Aufrufe
> umsetzen.

Für Photoshop-Plugins habe ich plain-old-winapi benutzt.
MFC ist eh nich so toll. Und Frameworks wie Qt haben einen eigene
main-loop. Geht also auch nicht.
Und ja, die switch-anweisung(en) sind teilweise pervers groß...

Thomas J. Gritzan

unread,
Jul 6, 2009, 5:22:18 PM7/6/09
to
Thomas Thiele schrieb:

> On 30 Jun., 20:04, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:
>
>>> Praktisches Beispiel: Behandlung von unterscheidlichen Lichtquellen
>>> (Infinite, Sphere, Projector) in Raytracing-Programm.
>>> Infinite hat keine Position, Sphere keine Richtung, Projector beides.
>>> Was tun?
>>> Ich habe daf�r keine L�sung um switch zu vermeinden.
>
>> Wozu brauchst du zum Rendern ein Interface f�r Position und Richtung?

>
> Verstehe die Frage nicht.
> Es ist nunmal so, dass die drei Lichtquellentypen (die ich benutzt)
> hatte,
> v�llig unterschiedlich behandelt werden mussten. Auch mathematisch.

> Und die Lichtquellen selber sollten nichts von eigentlichen rendern
> wissen und mitbekommen. Simple dumme Lichtquellen...

Die Lichtquellen k�mmern sich ja nur ums Beleuchten. Und die
"unterschiedliche Mathematik" ist in der virtuellen Funktion gekapselt.
Vom Rendern, also Kamera, Perspektive u.�. weiss die gar nichts. Aber
die Berechnungen, die f�r die Lichtquelle selbst durchgef�hrt werden
sollen, geh�ren nicht in eine switch-Anweisung, sondern in eine
virtuelle Funktion, zumindest wenn man OOP programmiert.

> Und diese Lichtquellen konnten wie alle Objekte im Raum verschoben
> und gedreht werden, waren also auch von der Basis Object abgeleitet.

Dann hat die Lichtquelle also Funktionen zum Bewegen etc. von Object,
und Funktion(en) zum Beleuchten von der Lichtquellen-Basis.

> Wie gesagt, ich habs letztendlich durch eine gro�e switch anweisung
> gel�st. F�r andere Ideen bin ich offen.
> Ich m�sste das Projekt mal aufbohren/weitermachen.
> So ein abgespekt (war mal vieil ausf�hrlicher) ist es ein n�tzlicher
> kleiner renderer f�r Grafik in Dialogen etc.


>
>> und die speziellen Klassen implementieren diese Funktion und bieten

>> Setter f�r die Eigenschaften der Lichtquelle an. Damit k�mmert sich die
>> Lichtquellen-Implementation um ihre Funktionalit�t.


>
> Finde ich vom Design nicht gut!
> Das ist OOP mit aller Gewalt. Das Gegenteil von dependency injection.

> Beide Extremf�lle finde ich ungl�cklich.

Das ist normales OOP, nix "mit aller Gewalt". Das Objekt k�mmert sich
genau um seine Aufgabe.
OOP ist nicht f�r alle Programme/Aufgabenstellungen gut geeignet, aber
Raytracing ist eigentlich ein Fall, wo es gut funktioniert.

> Im ersten Fall weiss die Klasse zuviel (sie soll *nur* Licht machen
> und

> nicht berechnen wie das Licht auf andere K�rper trifft, dort


> reflektiert
> wird und auf die Kamera trifft.) im zweiten viel zu wenig.

Dann lass sie nur Licht machen. Sie sollte also berechnen, wie hell ein
Punkt angestrahlt wird je nach Entfernung und Winkel.

>> Urgs... ein gro�er switch-Block, ich kenne das. Besonders �bersichtlich
>> oder wartbar ist es aber nicht. Zum Gl�ck gibt es ja GUI Frameworks, die


>> das ganze in Klassen kapseln und die Ereignisse in Callback-Aufrufe
>> umsetzen.
>

> F�r Photoshop-Plugins habe ich plain-old-winapi benutzt.


> MFC ist eh nich so toll. Und Frameworks wie Qt haben einen eigene
> main-loop. Geht also auch nicht.

> Und ja, die switch-anweisung(en) sind teilweise pervers gro�...

Da k�nnte man zum Beispiel ein std::map<> benutzen. Falls f�r eine
Window Message ein Callback eingetragen ist (::find() liefert nicht
::end(), wird dieser aufgerufen, ansonsten wird DefWindowProc aufgerufen.

--
Thomas

Thomas Thiele

unread,
Jul 7, 2009, 8:33:12 AM7/7/09
to
On 6 Jul., 23:22, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:

> Die Lichtquellen kümmern sich ja nur ums Beleuchten. Und die


> "unterschiedliche Mathematik" ist in der virtuellen Funktion gekapselt.

> Vom Rendern, also Kamera, Perspektive u.ä. weiss die gar nichts. Aber
> die Berechnungen, die für die Lichtquelle selbst durchgeführt werden
> sollen, gehören nicht in eine switch-Anweisung, sondern in eine


> virtuelle Funktion, zumindest wenn man OOP programmiert.

Das ist eine Frage der Modellierung.
Eine Lichtquelle, die nur passiv leuchtet, aber nicht aktiv beleuchtet
weiss genaugenommen nichts von der Mathematik.

Thomas J. Gritzan

unread,
Jul 7, 2009, 10:58:18 AM7/7/09
to
Thomas Thiele schrieb:

> On 6 Jul., 23:22, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:
>
>> Die Lichtquellen k�mmern sich ja nur ums Beleuchten. Und die

>> "unterschiedliche Mathematik" ist in der virtuellen Funktion gekapselt.
>> Vom Rendern, also Kamera, Perspektive u.�. weiss die gar nichts. Aber
>> die Berechnungen, die f�r die Lichtquelle selbst durchgef�hrt werden
>> sollen, geh�ren nicht in eine switch-Anweisung, sondern in eine

>> virtuelle Funktion, zumindest wenn man OOP programmiert.
>
> Das ist eine Frage der Modellierung.
> Eine Lichtquelle, die nur passiv leuchtet, aber nicht aktiv beleuchtet
> weiss genaugenommen nichts von der Mathematik.

Was soll eine Lichtquelle denn tun, die nur passiv leuchtet? Das ist
dann doch eher ein Datenbeh�lter (POD struct) mit Lichtfarbe,
Intensit�t, Richtung etc.

In der OOP hat man aber f�r gew�hnlich aktive gekapselte Objekte, die
alle ihre eigenen Aufgaben erledigen.

In C++ muss man nat�rlich nicht OOP programmieren, aber in diesem Fall
bietet es sich an.

--
Thomas

Thomas Thiele

unread,
Jul 8, 2009, 4:53:59 AM7/8/09
to
Thomas J. Gritzan schrieb:

> In der OOP hat man aber f�r gew�hnlich aktive gekapselte Objekte, die
> alle ihre eigenen Aufgaben erledigen.

Ich gebe dir recht.
Aber es bleibt es Sache der Modellierung, des Schnitts der Aufgaben.
Genauso gut k�nnte man die Mathematik in die Fl�che (z.B. Polygon)
verlagern die beleuchtet wird.

Wo findet das Beleuchten statt?
In der Lichtquelle? Von der Lichtquelle? Auf dem Polygon? Oder doch in
der Szenerie selbst? Die Lichtquelle kann die Beleuchtung gar nicht
komplett durchf�hren weil sie a) nicht die einzige ist b) nicht weiss
wie sie abgschattet wird c) den Winkel wie sie auf das Polygon trifft
und den Winkel der Kamera (f�r sepkularen Anteil wichtig) nicht kennt.

Die Lichtquelle ist bei mir also eine Objekt mit Eigenschaften.

Im Grunde ist es ein alten OOP Dilemma. Modelelliere ich m�glichst
analog zur Wirklichkeit oder modelliere ich so dass ich toll die
Mechanismen der OOP ausnutzen kann?
Ich pr�feriere pragmatisch beides je nach Fall.

0 new messages