Hallo,
ich habe hier mir eine art generischen Datentyp als
typedef struct typed_value_t
{
union {
float fval;
int ival;
unsigned int uval;
unsigned long lval;
} value_t;
char valtype;
}typed_value_t;
definiert. valtype entspricht dabei folgenden Werten:
enum types_e
{
t_int = 0,
t_float = 1,
t_uint = 2,
t_long = 3
};
nun möchte ich auf diesen Datentyp binäre Operationen durchführen, also
zum Beispiel die kleiner gleich Operation mit folgender Signatur:
int leq(typed_value_t* a,typed_value_t *b)
Muss ich da jetzt eine endlose Permutation aller Möglichkeiten mit "if"
durchgehen, oder kann man das irgendwie eleganter lösen?
LG, Stefan
stefan wrote:
> Muss ich da jetzt eine endlose Permutation aller Möglichkeiten mit "if"> durchgehen,
es wird dir nichts anderes übrig bleiben.
> oder kann man das irgendwie eleganter lösen?
Nein.
Das ist das Wesen derartiger Rundumschlag-Unions.
In C++ entsteht dieses Problem meist nicht, aufgrund üblicherweise
anderen Designs und/oder der Verwendung von Templates.
Ein exakter Ersatz wäre eine Konvertierungsfunktion in einen
allumfassenden Datentyp, und der wird dann vergleichen. Das geht mit C
auch, ist aber mit C++ übersichtlicher. Ist aber in jedem Fall
ineffizient.
In c++ könnte man z.B. einfach schreiben if (a<b), und der Compiler
findet alleine die richtige Operator-Funktion. Nur: Programmieren musst
du alle Varianten trotzdem, da kommst du nicht drumrum. Lediglich das
große if-Geraffel zur Auswahl fällt weg.
Bleibt die Frage, wofür du diese Monster-Union brauchst, und ob es dafür
nicht doch einen bessere Lösung gäbe.
Oliver
Noch einmal kurz zurück zu C:
Statt alle der 4²=16 Möglichkeiten für die Typen der beiden Argumente
abzufragen, könntest du eine Funktion schreiben, die die Werte
verlustfrei in double konvertiert. In dieser Funktion brauchst du nur
4 Fälle zu unterscheiden. Anschließend wird ein gewöhnlicher
double-Vergleich durchgeführt.
So, jetzt dürft ihr wieder mit C++ weitermachen :)
Andreas Kaiser wrote:
> In C++ entsteht dieses Problem meist nicht, aufgrund üblicherweise> anderen Designs und/oder der Verwendung von Templates.
Allerdings ist es in C++ genauso ekelhaft.
Das läuft normalerweise auf einen sog. "double dispatch"
hinaus (virtueller Aufruf einer Funktion basierend auf 2
Basispointer) und C++ hat dafür keine vernünftige, wartbare
Lösung parat.
Das beste, was ich dafür bis jetzt gefunden habe, ist eine
Tabellen-Lösung basierend auf der Run Time Type Information.
Ist aber nicht wirklich schön, im Sinne der OOP Philosophie.
Karl heinz Buchegger wrote:
> Allerdings ist es in C++ genauso ekelhaft.
Wenn man versucht, es so zu lösen wie in C, dann ja. Ich will eher
darauf hinaus, dass man in C++ eher die Anwendung bzw. die
Datenstrukturen anders designed und damit das Problem auf normale
virtual members zurückführen kann, vielleicht auch einfach nur auf
function templates.
yalu wrote:
> abzufragen, könntest du eine Funktion schreiben, die die Werte> verlustfrei in double konvertiert.
Blöd nur, dass auf AVR "double" nicht alle Werte von "long" erfassen
kann. Das ist zwar nicht standardkonform aber leider Fakt.
Andreas Kaiser wrote:
> Karl heinz Buchegger wrote:>>> Allerdings ist es in C++ genauso ekelhaft.>> Wenn man versucht, es so zu lösen wie in C, dann ja. Ich will eher> darauf hinaus, dass man in C++ eher die Anwendung bzw. die> Datenstrukturen anders designed und damit das Problem auf normale> virtual members zurückführen kann, vielleicht auch einfach nur auf> function templates.
Probiers aus.
Du landest beim erwähnten "double dispatch". Wenn ich dafür
eine vernünftige OOP Lösung hätte, die auch wartbar und
vrnünftig erweiterbar ist, würde ich mir das was kosten lassen :-)
Ich habe im Grunde ein genau identisches Problem bei zb folgender
Problemstellung:
Gegeben sind graphische Primitiva: Gerade, Kreis, Polygon, etc...
Ein geometrischer Editor soll nun in der Lage sein, jedes Primitiv
mit jedem zu verschneiden (Schnittpunkt berechnen).
Logischerweise sind alle Primitiva von einer gemeinsamen Basis-
Klasse abgeleitet.
class Primitiv
{
};
class Line : public Primitiv
{
};
class Circle : public Primitiv
{
};
Um da jetzt eine virtuelle Intersect Funktion einzubringen:
class Primitiv
{
public:
virtual PointSet Intersect( const Primitiv& With ) const;
};
Soweit so gut, damit kann man mal jetzt erst mal von der
Aufgabenstellung A.Intersect( B )
das A auflösen lassen:
class Line : public Primitiv
{
public:
virtual PointSet Intersect( const Primitiv& With ) const;
};
class Circle : public Primitiv
{
public:
virtual PointSet Intersect( const Primitiv& With ) const;
};
aber was ist mit dem B? Das zweite Objekt das in die Intersect
Funktion hineingeht ist ein Primitiv. Schon. Aber ist das jetzt
eine Line oder ein Circle?
Keine Ahnung, also machen wir den double dispatch:
PointSet Line::Intersect( const Primitiv& With ) const
{
return With.Intersect( *this );
}
PointSet Circle::Intersect( const Primitiv& With ) const
{
return With.Intersect( *this );
}
damit kommt der 2-te virtuelle Aufruf zustande. Damit das aber
jetzt endlich mal zu konkreten Datentypen führt (der Typ vom
this Pointer ist ja jeweils bekannt), benötigt man noch zusätzliche
Funktionen
PointSet Line::Intersect( const Line& With ) const
{
// Schnittpunktsberechnung für Line-Line
}
PointSet Line::Intersect( const Circle& With ) const
{
// Schnittpunktsberechnung für Line-Circle}
}
PointSet Circle::Intersect( const Line& With ) const
{
// Circle - Line ist dasselbe wie Line-Circle
return With.Intersect( *this );
}
PointSet Circle::Intersect( const Circle& With ) const
{
// Circle - Circle behandeln
}
Nur damit das überhaupt geht, muss die Basisklasse ebenfalls
erweitert werden:
class Primitiv
{
public:
virtual PointSet Intersect( const Primitiv& With ) const;
virtual PointSet Intersect( const Line& With ) const;
virtual PointSet Intersect( const Circle& With ) const;
};
und damit erhebt sich aber die Frage: Warum muss die Basisklasse
wissen, dass es 2 davon abgeleitete Klassen namens Line und Circle
gibt? Das ist nicht mehr wirklich OOP like.
Ganz abgesehen, dass es bei 2 Klassen 2 'double dispatch' Methoden
und 4 spezielle Intersect methoden gibt. Bei entsprechend mehr
abgeleiteten Klassen nimmt die Funktionsflut entsprechend zu
(Ich hatte in meinem graph. Editor 6 Primitivklassen und demenstspr.
viele Intersect Methoden) und irgendwann verliert man den
Überblick wer jetzt wen in welchem Fall aufruft.
Das ganze ist so einfach nur ekelhaft.
Nachdem ich mich eine Weile mit dem Problem herumgeärgert habe,
bin ich dann zu einer anderen Lösung gewechselt.
ich habe freistehende Intersect Funktionen und eine Tabelle,
die die Run Time Type Information der beiden Primtiva mit
der jeweils richtigen Funktion in Beziehung setzt. Die aufgerufene
Intersect Funktion stellt dann von beiden Primitiva den tatsächlichen
Typ fest, sucht diese Kombination in der Tabelle und ruft die
dazugehörige Intersect-Arbeitsfunktion auf.
Im Grunde nichts anderes als ein switch-case, wobei die cases
zwecks Wartbarkeit in eine Tabelle abgebildet sind. Allerdings
sind wir uns darüber einig, dass eines der Ziele von OOP darin
besteht, genau diese switch-case über einen Klassentyp durch
Inheritance zu ersetzen, weil dann ja die Wartbarkeit wesentlich
verbessert wird (ausser im Falle eines double dispatch).
Ein Teufelskreis.
Andreas Kaiser schrieb:
> yalu wrote:>>> abzufragen, könntest du eine Funktion schreiben, die die Werte>> verlustfrei in double konvertiert.>> Blöd nur, dass auf AVR "double" nicht alle Werte von "long" erfassen> kann. Das ist zwar nicht standardkonform aber leider Fakt.
Stimmt allerdings. Ich bin, als ich dieses geschrieben habe, ohne
nachzudenken von einer "größeren" Plattform (PC o.ä.) ausgegangen.
Bei diesen 8-Bit-Dingern gibt es natürlich einiges mehr zu
berücksichtigen.
Aber Stefan sollte vielleicht mal schreiben, auf welchem System das
Ganze tatsächlich laufen soll. Wenn es ein AVR ist, gibt es
möglicherweise auch bei C++-Lösungen Einschränkungen. Ohne es
ausprobiert zu haben bin ich mir fast sicher, dass der AVR-GCC bzw.
die AVR-Libc kein RTTI (wie es Karl Heinz zuletzt vorgeschlagen hat)
unterstützen.
Das ganze soll auf MSP430 und ARM7 laufen. Hab es jetzt mit nem
2Dimensionalen Array aus Funktionspointern gelöst, das ich über die
Werte aus types_e anspreche. Das erspart mir wenigstens die ganzen IFs.
LG, Stefan