Ist das hier guter Stil?



  • Naja, ich hab den Code nicht auf Anhieb verstanden. Fand es etwas befremdlich, das elem auf 2 verschiedene Typen gecasted wird. Hab ich vorher noch nie gesehen.

    Was ich unschoen finde ist, dass der gemeinsame Teil von COMMON_STRUCT und den ganzen "Kind" Structs exakt uebereinstimmen muss, damit die Offsets passen. Faengt z.B. COMMON_STRUCT mit einem int a, float b an, dann MUESSEN alle Kinder auch genauso anfangen. Drehe ich in der Deklaration a und b um, klappt es schon nicht mehr.
    Finde ich grenzwertiges Design.



  • @stilfrage:
    Du hast wohl noch nie was von vererbung gehört? 🙄
    @besenstil :
    zeig mal ein beispiel von OOP in C 😉



  • Da ist wohl einer nicht in der Lage, auf Verweise zu reagieren (noch einmal: GObject). Grösseren Gebrauch davon siehst du in jedem GTK+-Programm.



  • @stilfrage: Ich hatte mir so etwas vorgestellt:

    typedef struct COMMON_STRUCT {
            int a;
            float b;
    } COMMON_STRUCT;
    
    typedef struct LINE_STRUCT {
            COMMON_STRUCT parent;
            char *c;
    } LINE_STRUCT;
    
    typedef struct SPLINE_STRUCT {
            COMMON_STRUCT parent;
            long c[3];
    } SPLINE_STRUCT;
    
    /* Bemerkung: DIE_NAMEN_AUS_LAUTER_GROSSBUCHSTABEN_KAUM_AUSZUHALTEN_STRUCT */
    

    Damit erspart man sich das Problem, das du geschildert hast. Ich bin nur so etwas gewohnt. Wenn das bei dir nicht so ist, hast du völlig Recht mit deiner Feststellung.



  • stilfrage schrieb:

    Meine Frage: Das ist ja so eine Art Vererbung in C. Ist das nun guter Stil oder eher ein Hack?

    ja, das ist letztlich der Trick, der in der glib verwendet wird, um Vererbung zu erzeugen. (besenstil hat den Link zur Doku gepostet).

    Es ist aber verdammt einfach, Fehler zu machen. Deshalb wäre es ratsam, falls du so etwas machen willst, Makros oder Funktionen zu implementieren, die dir helfen, die Datentypen zu lesen. Z.b:

    #define IS_LINE_T(x) ( ((COMMON_STRUCT*) x)->parent && (((COMMON_STRUCT*) x)->type == LINE_T)
    
    #define IS_SPLINE_T(x) ( ((COMMON_STRUCT*) x)->parent && (((COMMON_STRUCT*) x)->type == SPLINE_T)
    ...
    
    static int foo(void *elem, double* s1, double* s2) {
      if(IS_LINE_T(elem))
        LINE_STRUCT *line = elem;
    ...
    }
    

    Du kannst ein ähnlcihes System wie in der GLIB verwenden. Schau dir diverse Bsp an, wie man in GTK+ programmiert.



  • supertux schrieb:

    besenstil hat den Link zur Doku gepostet

    Nein, hab ich nicht. Das hole ich jetzt nach: http://library.gnome.org/devel/gobject/unstable/

    supertux schrieb:

    stilfrage schrieb:

    Meine Frage: Das ist ja so eine Art Vererbung in C. Ist das nun guter Stil oder eher ein Hack?

    ja, das ist letztlich der Trick, der in der glib verwendet wird, um Vererbung zu erzeugen.

    Nein, der Trick ist _nicht_, dass ein int als Typeid verwendet wird. Der Trick ist eine zusätzliche Klasse.

    supertux schrieb:

    Deshalb wäre es ratsam, falls du so etwas machen willst, Makros oder Funktionen zu implementieren, die dir helfen, die Datentypen zu lesen. Z.b:

    ...
    
    static int foo(void *elem, double* s1, double* s2) {
      if(IS_LINE_T(elem))
        LINE_STRUCT *line = elem;
    ...
    }
    

    Igitt. Was du machst, entspricht einem reinterpret_cast<> in C++ und du kannst gerne in dem Forum dort nachfragen - der ist nicht gerne gesehen.



  • besenstil schrieb:

    @stilfrage: Ich hatte mir so etwas vorgestellt:

    typedef struct COMMON_STRUCT {
            int a;
            float b;
    } COMMON_STRUCT;
    
    typedef struct LINE_STRUCT {
            COMMON_STRUCT parent;
            char *c;
    } LINE_STRUCT;
    
    typedef struct SPLINE_STRUCT {
            COMMON_STRUCT parent;
            long c[3];
    } SPLINE_STRUCT;
    
    /* Bemerkung: DIE_NAMEN_AUS_LAUTER_GROSSBUCHSTABEN_KAUM_AUSZUHALTEN_STRUCT */
    

    Damit erspart man sich das Problem, das du geschildert hast. Ich bin nur so etwas gewohnt. Wenn das bei dir nicht so ist, hast du völlig Recht mit deiner Feststellung.

    Naja, aber wie soll ich bei dem Code jetzt den Typ ermitteln und entsprechend downcasten?

    supertux: Hm, ok. Ich bin noch unschluessig, ob das jetzt gut ist oder ein riesen Hack.



  • nachdem es noch keiner eingeworfen hat, bringe ich mal unions ins gespräch 😉



  • besenstil schrieb:

    supertux schrieb:

    besenstil hat den Link zur Doku gepostet

    Nein, hab ich nicht. Das hole ich jetzt nach: http://library.gnome.org/devel/gobject/unstable/

    Ich habe nur gesehen, dass GObject als Link markiert war, und bin davon ausgegangen, dass es die GObject Doku war.

    besenstil schrieb:

    Nein, der Trick ist _nicht_, dass ein int als Typeid verwendet wird. Der Trick ist eine zusätzliche Klasse.

    Das stimmt, das ist es nicht ganz, aber die Objekte in der glib basieren darauf, dass der Anfang aller Bytes immer gleich ist.

    struct _GtkWindowClass
    {
      GtkBinClass parent_class;
    ...
    

    und in gtkbin.h

    struct _GtkBinClass
    {
      GtkContainerClass parent_class;
    

    und in

    struct _GtkContainerClass
    {
      GtkWidgetClass parent_class;
    ...
    

    usw bis in gtype.h

    typedef gsize                           GType;
    ...
    struct _GTypeClass
    {
      /*< private >*/
      GType g_type;
    };
    

    alle basieren alle darauf, dass das erste Element des Structs stets ein gsize ist, und gsize ist wiederum ein unsigned long.

    besenstil schrieb:

    Igitt. Was du machst, entspricht einem reinterpret_cast<> in C++ und du kannst gerne in dem Forum dort nachfragen - der ist nicht gerne gesehen.

    Hier ist nun mal der C Forum und in C programmiert man halt anders. Stimmt, das was ich gemacht habe, ist häßlich, keine Frage, aber wenn man schon so etwas macht, dann ist das dennoch passend in meinen Augen.

    Wenn man unbedingt OO in C machen will, dann würde ich GObject zugreifen, denn das Framework hat eine große API für solche Sachen.



  • So wie ich das sehe, wäre der vernünftig objektorientierte Ansatz, statt der Typfrickelei gleichzeitig noch virtuelle Funktionen nachzubauen. Beispiel:

    #include <stdio.h>
    
    struct base {
      void (*foo)(void *this, double x);
    
      double x;
    };
    
    struct derived_1 {
      struct base parent;
      double y;
    };
    
    struct derived_2 {
      struct base parent;
      double z;
    };
    
    void base_foo     (void *this, double x) { printf("Base: %g\n", ((struct base *) this)->x + x); }
    void derived_1_foo(void *this, double x) {
      struct derived_1 *self = (struct derived_1 *) this;
    
      printf("Derived 1: %g\n", self->y + self->parent.x  + x);
    }
    
    void run_foo(struct base *p) {
      p->foo(p, 3.141);
    }
    
    int main(void) {
      struct base      b  =   {      base_foo, 2.0 };
      struct derived_1 d1 = { { derived_1_foo, 2.0 }, 3.0 };
      struct derived_2 d2 = { {      base_foo, 2.0 }, 1.0 }; /* foo nicht überladen */
    
      run_foo(&b);
      run_foo((struct base *) &d1);
      run_foo((struct base *) &d2);
    
      return 0;
    }
    

    Das ist jetzt natürlich eine sehr rudimentäre Implementation; in Produktionscode wäre es sinnvoll, die Initialisierung in einer eigenen Funktion vorzunehmen, und abhängig von der Anzahl "virtueller Methoden" kann es auch Sinn machen, eine statische Funktionstabelle für jeden Typ aufzubauen und jeder Instanz nur einen Zeiger darauf mitzugeben - so wird das von C++-Compilern üblicherweise gemacht.

    Auch wenn in C andere Regeln gelten als in C++, die Notwendigkeit eines Downcasts bei der Umsetzung eines objektorientierten Modells bedeutet mit ziemlicher Sicherheit einen Designfehler in diesem Modell. Grundsätzlich soll Code, der einen Zeiger auf eine Basisklasse bekommt, sich nicht darum scheren müssen (und es auch nicht tun), welche konkrete Klasse hinter diesem Zeiger steckt. So kann man an anderer Stelle weitere Klassen ableiten, ohne den Clientcode ändern zu müssen.


Anmelden zum Antworten