Frage

Was würde eine Reihe von geschickten Präprozessor Hacks (ANSI C89 / ISO C90 kompatibel) sein, die eine Art von hässlichen (aber brauchbar) Objektorientierung in C aktivieren?

Ich bin mit ein paar anderen objektorientierten Sprachen, so wenden Sie sich bitte antworten Sie nicht mit Antworten wie „Learn C ++“. Ich habe „ Objektorientierte Programmierung mit ANSI C “(Achtung: PDF-Format ) und einige andere interessante Lösungen, aber ich bin meistens interessiert sich für Sie folgende Aufgaben: -)


Siehe auch schreiben können Sie objektorientierten Code in C schreiben?

War es hilfreich?

Lösung

C Object System (COS) klingt vielversprechend (es ist immer noch in der Alpha-Version). Es wird versucht, minimal verfügbare Konzepte aus Gründen der Einfachheit und Flexibilität zu halten: einheitliche objektorientierte Programmierung mit offenen Klassen, metaclasses, Eigentum metaclasses, Generika, Multimethoden, Delegation, Eigentum, Ausnahmen, Verträge und Verschlüsse. Es gibt ein Entwurf Papier (PDF), die es beschreibt.

Ausnahme in C ist eine C89 Umsetzung des try-catch-finally in anderen OO-Sprachen gefunden. Es kommt mit einer Testsuite und einige Beispiele.

Sowohl durch Laurent Deniau, die viel auf OOP in C .

Andere Tipps

würde ich abraten Präprozessor (ab) verwenden, um zu versuchen und C-Syntax eher wie die einer anderen mehr objektorientierte Sprache zu machen. Auf der untersten Ebene, Sie einfach nur structs als Objekte verwenden und geben sie um durch Zeiger:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

Um die Dinge wie Vererbung und Polymorphismus zu erhalten, müssen Sie ein wenig härter arbeiten. Sie können das erste Element einer Struktur manuelle Vererbung tun sein, indem eine Instanz der Oberklasse, und dann können Sie Zeiger auf Basis umgossen und abgeleiteten Klassen frei:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

Um Polymorphismus (das heißt virtuelle Funktionen) zu erhalten, verwenden Sie Funktionszeiger und optional Funktionszeiger-Tabellen, die auch als virtuelle Tabellen oder vtables bekannt:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

Und das ist, wie Sie Polymorphismus tun in C. Es ist nicht schön, aber es macht den Job. Es gibt einige klebrigen Probleme Zeiger beteiligt wirft zwischen Basis und abgeleiteten Klassen, die so lange sicher sind als die Basisklasse das erste Mitglied der abgeleiteten Klasse ist. Mehrfachvererbung ist viel schwieriger -. In diesem Fall um Fall zwischen Basisklassen außer den ersten, müssen Sie manuell Ihre Zeiger basierend auf den richtigen Offsets einzustellen, was wirklich schwierig ist und fehleranfällig

Weitere (tricky), was Sie tun können, ist die dynamische Typ eines Objekts zur Laufzeit ändern! Sie können es nur ein neuer VTable-Zeiger zuweisen. Sie können sogar selektiv einige der virtuellen Funktionen ändern, während andere zu halten, neue Hybrid-Typen zu schaffen. Nur vorsichtig sein, ein neues VTable zu erstellen, anstatt die globale VTable modifizieren, sonst versehentlich alle Objekte eines bestimmten Typs beeinflussen werden.

Ich habe einmal mit einer C-Bibliothek, die in eine Art und Weise durchgeführt wurde, die ich als recht elegant geschlagen. Sie hatten geschrieben, in C, eine Möglichkeit, Objekte zu definieren, dann von ihnen erben, so dass sie als erweiterbare als C ++ Objekt waren. Die Grundidee war:

  • Jedes Objekt hat seine eigene Datei
  • öffentliche Funktionen und Variablen in der H-Datei für ein Objekt definiert werden
  • Privat Variablen und Funktionen wurden nur in der C-Datei
  • befindet
  • Um „erbt“ eine neue Struktur ist mit dem ersten Elemente der Struktur erstellt, das Objekt zu sein von erben

Vererben ist schwer zu beschreiben, aber es war im Grunde diese:

struct vehicle {
   int power;
   int weight;
}

Dann in einer anderen Datei:

struct van {
   struct vehicle base;
   int cubic_size;
}

Dann könnte man einen Van im Speicher erstellt hat, und von Code verwendet werden, die nur über Fahrzeuge wußte:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

Es funktionierte wunderbar, und die H-Dateien definieren genau das, was Sie sollen mit jedem Objekt der Lage zu tun.

Der GNOME-Desktop für Linux geschrieben wird in der objektorientierten C, und es hat ein Objektmodell namens " GObject " die Eigenschaften, Vererbung, Polymorphismus unterstützt, sowie einige andere Goodies wie Referenzen, Event-Handling (genannt "Signale"), Laufzeit Typisierung, private Daten, etc.

Es umfasst Präprozessor Dinge wie eine Schublade gesteckt um in der Klassenhierarchie zu tun Hacks etc. Hier ist ein Beispiel der Klasse I für GNOME geschrieben (Dinge wie gchar sind typedefs):

Klasse Quelle

Klasse-Header

Im Innern der GObject Struktur gibt es eine GType ganze Zahl, die als magische Zahl für GLib dynamische Typisierung System verwendet wird (Sie die gesamte Struktur auf einen „GType“ werfen kann zu finden Typ ist).

Wenn Sie Methoden denken auf Objekte als statische Methoden genannt, die eine implizite ‚this‘ gehen in die Funktion kann sie denken OO in C leichter machen.

Zum Beispiel:

String s = "hi";
System.out.println(s.length());

wird:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

oder so ähnlich.

Ein wenig Off-Topic, aber die ursprüngliche C ++ Compiler, Cfront , kompilierte C ++ zu C und dann in Assembler.

hier .

Ich habe diese Art der Sache in C zu tun, bevor ich wusste, was OOP war.

Es folgt ein Beispiel, das einen Datenpuffer implementiert, die auf Anforderung, da eine Mindestgröße, Schrittweite und maximale Größe wächst. Diese besondere Implementierung war „Element“ basiert, die es zu sagen ist wurde entwickelt, um eine Liste artige Sammlung von jedem C-Typ zu ermöglichen, nicht nur eine variable Längen-Byte-Puffer.

Die Idee ist, dass das Objekt instanziiert wird die xxx_crt () verwendet und gelöscht mit xxx_dlt (). Jeder der „Mitglied“ Methoden nimmt einen speziell typisierten Zeiger arbeiten auf.

ich implementiert eine verknüpfte Liste, zyklische Puffer, und eine Reihe anderer Dinge auf diese Weise.

Ich muß gestehen, ich habe niemals einen Gedanken darüber, wie gegeben Erbe mit diesem Ansatz zu implementieren. Ich kann mir vorstellen, dass einige Mischung aus, dass durch Kieveli angeboten könnte ein guter Weg sein.

dtb.c:

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

dtb.h

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

PS: Vint war einfach ein typedef int - Ich benutze es, mich daran zu erinnern, dass es Länge von Plattform zu Plattform variabel war (für die Portierung)

.

ffmpeg (ein Toolkit für die Videoverarbeitung) in geraden C (und Assembler-Sprache) geschrieben, aber Verwendung eines objektorientierten Design. Es ist voll von structs mit Funktionszeigern. Es gibt eine Reihe von Fabrik Funktionen, die die Strukturen mit den entsprechenden „Methode“ Zeiger initialisieren.

Wenn Sie wirklich catefully denken, auch Standard-C-Bibliothek verwenden OOP - betrachten FILE * als Beispiel: fopen() initialisiert ein FILE * Objekt, und verwenden Sie es Mitglied Methoden verwenden fscanf(), fprintf(), fread(), fwrite() und andere, und es schließlich zum Abschluss bringen mit fclose().

Sie können auch mit dem pseudo-Objective-C Weg gehen, die auch nicht schwer ist:

typedef void *Class;

typedef struct __class_Foo
{
    Class isa;
    int ivar;
} Foo;

typedef struct __meta_Foo
{
    Foo *(*alloc)(void);
    Foo *(*init)(Foo *self);
    int (*ivar)(Foo *self);
    void (*setIvar)(Foo *self);
} meta_Foo;

meta_Foo *class_Foo;

void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
    class_Foo = malloc(sizeof(meta_Foo));
    if (class_Foo)
    {
        class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
    }
}

Foo *__imp_Foo_alloc(void)
{
    Foo *foo = malloc(sizeof(Foo));
    if (foo)
    {
        memset(foo, 0, sizeof(Foo));
        foo->isa = class_Foo;
    }
    return foo;
}

Foo *__imp_Foo_init(Foo *self)
{
    if (self)
    {
        self->ivar = 42;
    }
    return self;
}
// ...

So verwenden:

int main(void)
{
    Foo *foo = (class_Foo->init)((class_Foo->alloc)());
    printf("%d\n", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d\n", (foo->isa->ivar)(foo)); // 60
    free(foo);
}

Dies ist, was kann von einem Objective-C Code wie folgt geführt werden, wenn ein ziemlich alter Objective-C-to-C-Übersetzer verwendet wird:

@interface Foo : NSObject
{
    int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end

@implementation Foo
- (id)init
{
    if (self = [super init])
    {
        ivar = 42;
    }
    return self;
}
@end

int main(void)
{
    Foo *foo = [[Foo alloc] init];
    printf("%d\n", [foo ivar]);
    [foo setIvar:60];
    printf("%d\n", [foo ivar]);
    [foo release];
}

Ich denke, was Adam Rosenfield geschrieben ist der richtige Weg, OOP in C zu tun Ich mag würde hinzufügen, dass, was er zeigt die Implementierung des Objekts ist. Mit anderen Worten: die tatsächliche Umsetzung würde in der .c Datei gesetzt werden, während die Schnittstelle in der Header-Datei .h gesetzt werden würde. Zum Beispiel mit dem Affen Beispiel oben:

Die Schnittstelle würde wie folgt aussehen:

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

    //memory management
    monkey * monkey_new();
    int monkey_delete(monkey *thisobj);
    //methods
    void monkey_dance(monkey *thisobj);

Sie können in der Schnittstelle .h Datei sehen Sie nur die Definition Prototypen sind. Anschließend können Sie die Implementierungsteil „.c Datei“ in eine statische oder dynamische Bibliothek kompilieren. Dies schafft Verkapselung und auch können Sie die Implementierung nach Belieben ändern. Der Benutzer des Objekts braucht fast nichts über die Umsetzung der es zu wissen. Dies hat auch Orte konzentrieren sich auf das Gesamtdesign des Objekts.

Es ist meine persönliche Überzeugung, dass oop ist ein Weg, um Ihre Code-Struktur von Konzeptualisierung und Wiederverwertbarkeit und hat wirklich nichts mit den anderen Dingen zu tun, die zu c hinzugefügt werden ++ wie Überlastung oder Vorlagen. Ja die sind sehr schön nützliche Features, aber sie sind nicht repräsentativ für welche die objektorientierte Programmierung wirklich ist.

Meine Empfehlung: Halten Sie es einfach. Eines der größten Probleme, die ich habe ist ältere Software (manchmal über 10 Jahre alt) beibehalten wird. Wenn der Code nicht einfach ist, kann es schwierig sein. Ja, man kann mit Polymorphismus in C sehr nützlich OOP schreiben, aber es kann schwer zu lesen sein.

Ich ziehe einfache Objekte, die eine gut definierte Funktionalität kapseln. Ein gutes Beispiel hierfür ist glib2 , beispielsweise eine Hash-Tabelle:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

Die Tasten sind:

  1. Einfache Architektur und Design-Muster
  2. Erzielt grundlegende OOP Kapselung.
  3. Einfach zu implementieren, lesen, verstehen und pflegen

Wenn ich gehen OOP in CI schreiben wahrscheinlich mit einem pseudo- Pimpl gehen Design. Anstelle von Zeigern auf structs vorbei, erhalten Sie Zeiger auf Zeiger nach oben vorbei Structs. Dies macht den Inhalt undurchsichtig und erleichtert Polymorphismus und Vererbung.

Das eigentliche Problem mit OOP in C ist, was passiert, wenn Variablen Umfang verlassen. Es gibt keine Compiler generierte Destruktoren und Probleme verursachen können. Makros können möglicherweise helfen, aber es ist immer hässlich sein würde, zu betrachten.

Eine andere Art und Weise in einem objektorientierten Stil mit C zu programmiert ist, einen Code-Generator zu verwenden, die eine domänenspezifische Sprache C. verwandelt Wie es mit Typoskript und JavaScript zu bringen OOP zu js getan hat.

#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

Ausgabe:

6.56
13.12

Hier ist eine Show von dem, was OO-Programmierung mit C.

Das ist real, reine C, keine Präprozessormakros. Wir haben Erbe,     Polymorphismus und Datenkapselung (einschließlich Daten privat zu Klassen oder Objekte).     Es gibt keine Chance für geschützten Qualifier äquivalent, das heißt,     Private Daten sind zu der innheritance Kette privater hinunter.     Aber dies ist nicht ein Nachteil, weil ich nicht glaube, es ist notwendig.

CPolygon ist nicht instanziert, weil wir es nur verwenden, um Objekte zu manipulieren     der innheritance Kette nach unten, die gemeinsamen Aspekte haben aber unterschiedliche     Implementierung von ihnen (Polymorphismus).

@ Adam Rosenfield hat eine sehr gute Erklärung, wie OOP mit C zu erreichen

Außerdem würde ich empfehlen Ihnen zu lesen

1) pjsip

Eine sehr gute C-Bibliothek für VoIP. Sie können lernen, wie es OOP obwohl structs und Funktionszeiger Tabellen

erreicht

2) iOS Runtime

Erfahren Sie, wie iOS Runtime Befugnisse Objective C. Es erreicht OOP durch isa Zeiger, meta-Klasse

Für mich Orientierung in C-Objekt sollten diese Eigenschaften haben:

  1. Encapsulation und Verstecken von Daten (erreicht werden kann structs / opaque Zeiger verwenden)

  2. Vererbung und Unterstützung für Polymorphismus (Einfachvererbung kann mit structs erreicht werden - stellen Sie sicher, dass die abstrakte Basis ist nicht instantiable)

  3. Constructor und destructor Funktionalität (nicht leicht zu erreichen)

  4. Typprüfung (zumindest für benutzerdefinierte Typen wie C erzwingt keine)

  5. Referenzzählung (oder etwas implementieren RAII )

  6. Eingeschränkte Unterstützung für die Ausnahmebehandlung (setjmp und longjmp)

Auf die oben auf ANSI / ISO-Spezifikationen beruhen sollte und nicht darauf verlassen, Compiler-spezifische Funktionalität.

Lesen Sie http://ldeniau.web.cern.ch/ ldeniau / html / oopc / oopc.html . Wenn nichts anderes durch die Dokumentation zu lesen ist eine aufschlussreiche Erfahrung.

Ich bin ein bisschen spät, um die Partei hier aber Ich mag zu vermeiden beide Makro Extreme - zu viele oder zu viel verschleiert Code, aber ein paar offensichtliche Makros kann der OOP Code leichter zu entwickeln und lesen:

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

Ich denke, das eine gute Balance hat, und die Fehler es (zumindest mit Standard-gcc 6.3 Optionen) für einige der eher Fehler sind hilfreich statt verwirrend erzeugt. Der springende Punkt ist nicht die Produktivität der Programmierer zu verbessern?

Ich arbeite auch auf diesem basierend auf einer Makro-Lösung. So ist es für die tapfersten nur, ich denke ;-) Aber es ist ganz nett schon, und ich arbeite bereits an einigen Projekten oben drauf. Es funktioniert so, dass Sie zunächst eine separate Header-Datei für jede Klasse definieren. Wie folgt aus:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            \
    METHOD(Point,public,int,move_up,(int steps)) \
    METHOD(Point,public,void,draw)               \
                                                 \
    VAR(read,int,x,JSON(json_int))               \
    VAR(read,int,y,JSON(json_int))               \

Um die Klasse zu implementieren, erstellen Sie eine Header-Datei für sie und eine C-Datei, in der Sie die Methoden implementieren:

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d\n", self->x, self->y);
}

In der Kopfzeile Sie für die Klasse erstellt, sind Sie andere Header, die Sie brauchen, und definieren Arten usw. der Klasse verwandt. Sowohl in den Klasse-Header und in der C-Datei enthalten, die Sie die Klasse Spezifikationsdatei (siehe das erste Codebeispiel) und einen X-Makro. Dieser X-Makros ( 1 , < a href = "https://github.com/plainC/wondermacros/blob/master/wondermacros/objects/x/object_instance.h" rel = "nofollow noreferrer"> 2 , 3 etc.) wird der Code der tatsächlichen Klasse structs erweitern und andere Erklärungen.

Um eine Klasse, #define SUPER supername erbt und supername__define \ als die erste Zeile in der Klassendefinition hinzuzufügen. Beide müssen da sein. Es gibt auch JSON-Unterstützung, Signale, abstrakte Klassen, etc.

Um ein Objekt zu erstellen, nur W_NEW(classname, .x=1, .y=2,...) verwenden. Die Initialisierung basiert auf struct Initialisierung in C11 eingeführt. Es funktioniert gut und alles nicht aufgeführt ist auf Null gesetzt.

Um eine Methode, die Verwendung W_CALL(o,method)(1,2,3) zu nennen. Es sieht aus wie eine höhere Abruffunktion, aber es ist nur ein Makro. Es dehnt sich ((o)->klass->method(o,1,2,3)) was eine wirklich schöne Hack ist.

Siehe Dokumentation und die Code selbst .

Da der Rahmen etwas vorformulierten Code muss, habe ich ein Perl-Skript (Wobjects), die die Arbeit erledigt. Wenn Sie verwenden, können Sie einfach schreiben

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

, und es wird die Klasse Spezifikationsdatei, Klasse-Header und eine C-Datei erstellen, die enthält Point_impl.c, wo Sie die Klasse implementieren. Es spart eine ganze Menge Arbeit, wenn Sie viele einfache Klassen haben, aber immer noch alles in C Wobjects ist eine sehr einfache regulären Ausdruck basiert Scanner, die auf die spezifischen Bedürfnisse anzupassen einfach ist, oder von Grund auf neu geschrieben werden.

Das Open-Source-Projekt Dynace tut genau das. Es ist unter https://github.com/blakemcbride/Dynace

Wenn Sie ein wenig Code schreiben try this: https://github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d\n", p->age);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top