Forum: Compiler & IDEs Vergleich von unbekannten Typen


von stefan (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Andreas K. (a-k)


Lesenswert?

Alternative: Befasse dich mit C++.

von stefan (Gast)


Lesenswert?

Kannst Du mir da einen etwas weitergehenden Tipp geben wie das mit c++ 
zu lösen wäre?

von Andreas K. (a-k)


Lesenswert?

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.

von OliverSo (Gast)


Lesenswert?

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

von yalu (Gast)


Lesenswert?

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 :)

von Karl H. (kbuchegg)


Lesenswert?

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.

von Andreas K. (a-k)


Lesenswert?

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.

von Andreas K. (a-k)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von yalu (Gast)


Lesenswert?

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.

von stefan (Gast)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.