OOP-Denkweise "vergessen"



  • Hallo,

    üblicherweise programmiere ich in OO-Programmiersprachen. Nun bin ich jedoch dazu gezwungen, in einer nicht-OO-Sprache etwas zu programmieren, was für mich in C++ etc. kein Problem darstellen würde. In C aber beispielsweise fällt mir keine elegante Lösung für das Problem ein.

    Gegeben ist folgendes Problem:
    Wir haben einen Beutel gegeben, der unterschiedliche Gegenstände aufnehmen kann. In C++ würde ich einfach eine Klasse für den Beutel und eine abstrakte Klasse "Gegenstand" anlegen. Der Beutel hat dann einfach einen std::vector von Gegenständen. Was die Gegenstände konkret sind, ist dem Beutel dann ja egal.
    In C aber gibt es keine abstrakten Klassen. Wie stelle ich also dem Beutel eine Sammlung von Gegenständen bereit, wenn ich nur die konkreten Gegenstände (z. B. Taschentuch, Feuerzeug, Brille etc.) habe, wobei die Anzahl unterschiedlicher Gegenstände sehr schnell anwachsen kann?

    Meine Idee wäre nun, einfach eine Struktur "Gegenstand" anzulegen, die neben allgemeinen Attributen noch zwei weitere enthält: "Typ" (also "Taschentuch", "Feuerzeug", "Brille" etc.) und einen Data-Pointer, der auf eine Instanz des eigentlichen Gegenstandes zeigt (also z. B. ein Pointer auf eine Instanz der Taschentuch-Struktur).

    Dann kann ich in einer Funktion für einen Gegenstand des Beutels das Typ-Attribut abfragen und so entscheiden, wie weiter zu verfahren ist. Das Data-Feld wird dann auf die entsprechend korrekte Struktur gecastet und es wird mit der korrekten Struktur weiteres getan.

    Das Vorgehen erscheint mir aber unperformant (weil viele Abfragen bezüglich des Typs notwendig sind) und ggf. auch eher "hässlich" (wegen der nötigen Casts).

    Lässt sich mein Problem eleganter lösen oder ist mein Vorgehen der Standardweg?



  • Gehts dir konkret um C oder ist es dann im Endeffekt wieder eine völlig andere Sprache? Ich weiß auch nicht so genau, wie idiomatisches C aussieht, habs noch nie "richtig" programmiert. Viel angepasst und gewartet, aber keine eigenständigen größeren Projekte.
    Mir fallen erstmal zwei Sachen ein. Zum einen wird es so wie du es beschrieben hast schon öfter gemacht, bzw. so ähnlich. Die ganze Windows API ist so aufgebaut. Nur gibts kein Zeiger auf die weiteren Daten, sondern die Struktur selbst enthält weitere Daten, z.B. so:

    struct ItemBase
    {
      ItemType type;
    };
    
    struct TheItem
    {
      ItemType type;
      int weight;
      int color;
    };
    

    Du kriegst einen Zeiger auf so eine Struktur, schaust, was für einen Typ das hat und castest dann. Meist gibts noch einen size Member. Dann brauchst du kein weiteres Data Feld. Spricht aus meiner Sicht jetzt aber auch nicht unbedingt was dagegen (außer, dass das etwas mehr Speicher braucht).

    Die andere Idee wäre, objektorientiert in C zu programmieren. Das geht auch und wird durchaus auch gemacht. Nur musst es von Hand machen und virtuelle Funktionen z.B. durch Funktionszeiger ersetzen.



  • Man kann sich sowas wie Ableitung und vtables natürlich emulieren. Sowas findet man auch im Linux-Kernel. Da kann der Linus noch so dolle auf C++ rumhacken. Er (oder auch wer anderes) hat im Endeffekt Vererbung und virtuelle Destruktoren im Linux-Kern für Gerätetreiber IIRC. Die Jungs und Mädels definierten sich auch noch ein paar Makros dazu, die den Code (wenn man die Makro-Definitionen mal ignoriert) auch nicht mehr ganz so "hackig" aussehen lassen.

    Aber vielleicht tut es bei Dir auch schon etwas simpleres. Wenn ich da jetzt an ein Inventar bei einem Adventure-Spiel denke, dann ist dieser OOP-Kram meist überflüssig. Denn da unterscheiden sich die Gegenstände vom Typ her nicht wirklich. Sie haben nur andere Belegungen ihrer Eigenschaften: ein anderes Bild, eine andere Bezeichnung, eine andere ID und das wars. Da würde ja ein struct Gegenstand reichen.

    Ich würde mir also an Deiner Stelle überlegen, ob du so einen "Laufzeitpolymorphismus" wirklich brauchst. Und selbst wenn du für einen bestimmten Gegenstand spezielle zusätzliche Attribute brauchst, kann man die ja in Form einer allgemeinen Schlüssel/Wert-Kette anhängen:

    union Any {
        int i;
        double d;
        const char* s;
    };
    
    struct ExtAttrib {
        char const* key;
        Any value;  // <-- ist so natürlich nicht besonders typsicher :(
        struct More* next_attr;
    };
    
    struct Gegenstand {
        int id;
        char const* name;
        struct Icon* icon;
        struct ExtAttrib* next_attr;
    };
    

    ...ist natürlich alles recht frickelig und fragil -- mal davon abgesehen, dass dieser Codefetzen alles andere als selbstdokumentierend ist, was Besitzverhältnisse angeht und wer was wieder löschen muss.



  • C bietet doch alles was du brauchst.

    Klassen: struct
    CTOR, DTOR: Create und Destroy Funktion, erstellt bzw. löscht Instanz der struct
    Methoden: Funktionen, die als ersten Parameter einen struct Pointer annehmen
    Polymorphismus: Funktionszeiger, der je nach tatsächlichem Typ vom CTOR befüllt wird
    statt Ableitungen kannst du z.B. Aggregation verwenden
    usw...

    Habe früher viel mit C programmiert, und ich kann dir sagen, dass das auch alles kein Problem war. Vor allem geht man viele Dinge eben ein wenig pragmatischer an. Da gibts halt dann keine abstrakte Basisklasse oder ähnliches, sondern wie oben vorgeschlagen eine Struktur mit einem Typ (enum), und vielleicht noch ein union, in dem je nach Typ unterschieliche Daten drinnen sind.
    Dazu noch ein paar asserts um logische Fehler zu vermeiden und etwas Zurückhaltung bei der Ausnutzung von wilder Zeigerarithmetik.



  • gfgdfgfdgdfgdg schrieb:

    C bietet doch alles was du brauchst.

    Klassen: struct
    CTOR, DTOR: Create und Destroy Funktion, erstellt bzw. löscht Instanz der struct
    Methoden: Funktionen, die als ersten Parameter einen struct Pointer annehmen
    Polymorphismus: Funktionszeiger, der je nach tatsächlichem Typ vom CTOR befüllt wird
    statt Ableitungen kannst du z.B. Aggregation verwenden
    usw...

    Die Frage war, wie man das *sinnvoll* umsetzt, d.h. auf wieviel OOP convenience verzichte ich, um noch einigermaßen lesbare C Programme zu erhalten (würde mich auch interessieren). Dass man sich die meisten C++ Features in C hässlich nachbauen kann, ist mir z.B. auch bekannt.


Anmelden zum Antworten