170 votes

Orientation objet en C

Quelqu'un pourrait-il partager un ensemble d'astuces de préprocesseur (compatible ANSI C89/ISO C90, s'il vous plaît) qui permettent une sorte d'orientation objet moche (mais utilisable) en C ? Je suis familier avec quelques langages orientés objet différents, alors s'il vous plaît, ne répondez pas par des réponses du type "Apprenez le C++". J'ai lu " Programmation orientée objet avec ANSI C "(Attention : pdf ) et plusieurs autres solutions intéressantes, mais je suis surtout intéressé par la vôtre :-) !


Voir aussi Pouvez-vous écrire du code orienté objet en C ?

1 votes

Je peux répondre pour apprendre le D et utiliser le c compatible abi pour les endroits où tu as vraiment besoin du C. digitalmars.com/d

0 votes

Pas vraiment. Je travaille avec des systèmes embarqués qui n'ont qu'un compilateur C à leur disposition.

2 votes

@Dinah : Merci pour le "Voir aussi". Ce billet était intéressant.

6voto

jjnguy Points 62123

Si vous considérez les méthodes appelées sur des objets comme des méthodes statiques qui transmettent une valeur implicite ' this dans la fonction, il est plus facile de penser OO en C.

Par exemple :

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

devient :

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

Ou quelque chose comme ça.

6 votes

@Artelius : Bien sûr, mais parfois l'évidence ne l'est pas, jusqu'à ce qu'elle soit énoncée. +1 pour ça.

1 votes

Mieux encore, ce serait string->length(s);

6voto

Lawrence Dol Points 27976

Je faisais ce genre de choses en C, avant de savoir ce qu'était la POO.

Voici un exemple qui met en œuvre un tampon de données qui croît à la demande, en fonction d'une taille minimale, d'un incrément et d'une taille maximale. Cette implémentation particulière était basée sur des "éléments", c'est-à-dire qu'elle était conçue pour permettre une collection de type liste de n'importe quel type C, et pas seulement un tampon d'octets de longueur variable.

L'idée est que l'objet est instancié en utilisant la fonction xxx_crt() et supprimé en utilisant la fonction xxx_dlt(). Chacune des méthodes "membres" prend un pointeur spécifiquement typé pour fonctionner.

J'ai implémenté une liste liée, un tampon cyclique, et un certain nombre d'autres choses de cette manière.

Je dois avouer que je n'ai jamais réfléchi à la manière d'implémenter l'héritage avec cette approche. J'imagine qu'un mélange de celle proposée par Kieveli pourrait être une bonne voie.

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 était simplement un typedef de int - je l'ai utilisé pour me rappeler que sa longueur était variable de plateforme en plateforme (pour le portage).

7 votes

Bon sang, ça pourrait gagner un concours de C obscurci ! j'aime ça ! :)

0 votes

@horseyguy Non, ça ne peut pas. Il a été publié. De plus, ils considèrent l'inclusion de fichiers d'en-tête comme un abus contre l'outil iocccsize. Ce n'est pas non plus un programme complet. 2009 n'a pas eu de concours donc on ne peut pas comparer avec iocccsize. Le CPP a été abusé de nombreuses fois aussi, il est donc assez vieux. Etc. Désolé. Je n'essaie pas d'être négatif, mais je suis réaliste. Je comprends un peu ce que vous voulez dire, c'est une bonne lecture et j'ai voté en haut. (Et oui, j'y participe et oui, je gagne aussi).

4voto

Mr Fooz Points 21092

ffmpeg (une boîte à outils pour le traitement vidéo) est écrit en C (et en langage d'assemblage), mais en utilisant un style orienté objet. Il est rempli de structures avec des pointeurs de fonctions. Il existe un ensemble de fonctions d'usine qui initialisent les structures avec les pointeurs de "méthode" appropriés.

0 votes

Je ne vois pas de fonctions d'usine dans le programme (ffmpeg), et il ne semble pas utiliser le polymorphisme ou l'héritage (méthode triviale suggérée ci-dessus).

0 votes

Avcodec_open est une fonction d'usine. Elle place des pointeurs de fonction dans une structure AVCodecContext (comme draw_horiz_band). Si vous regardez l'utilisation de la macro FF_COMMON_FRAME dans avcodec.h, vous verrez quelque chose qui ressemble à l'héritage des membres de données. IMHO, ffmpeg me prouve que la POO est mieux faite en C++, pas en C.

3voto

Maxthon Chan Points 751

Si vous pensez vraiment que la bibliothèque C standard utilise la POO, considérez les points suivants FILE * à titre d'exemple : fopen() initialise un FILE * et vous l'utilisez en utilisant les méthodes membres fscanf() , fprintf() , fread() , fwrite() et d'autres, et finalement le finaliser avec fclose() .

Vous pouvez également opter pour la méthode pseudo-Objective-C qui n'est pas difficile non plus :

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;
}
// ...

A utiliser :

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);
}

C'est ce qui peut résulter d'un code Objective-C comme celui-ci, si l'on utilise un traducteur Objective-C vers C assez vieux :

@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];
}

0 votes

Qu'est-ce que __attribute__((constructor)) faire void __meta_Foo_init(void) __attribute__((constructor)) ?

1 votes

Il s'agit d'une extension GCC qui s'assure que la fonction marquée est appelée lorsque le binaire est chargé en mémoire. @A.E.Drew

0 votes

popen(3) renvoie également un FILE * pour un autre exemple.

3voto

user2683024 Points 53

Ma recommandation : restez simple. L'un des plus gros problèmes que je rencontre est la maintenance de logiciels anciens (parfois âgés de plus de 10 ans). Si le code n'est pas simple, cela peut être difficile. Oui, on peut écrire une POO très utile avec du polymorphisme en C, mais cela peut être difficile à lire.

Je préfère les objets simples qui encapsulent une fonctionnalité bien définie. Un excellent exemple de cela est GLIB2 par exemple une table de hachage :

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

Les clés sont :

  1. Architecture et modèle de conception simples
  2. Réalise l'encapsulation de base de la POO.
  3. Facile à mettre en œuvre, à lire, à comprendre et à entretenir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X