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.

198voto

Adam Rosenfield Points 176408

Je déconseille l'(ab)utilisation du préprocesseur pour essayer de rendre la syntaxe du C plus proche de celle d'un autre langage plus orienté objet. Au niveau le plus basique, on utilise simplement des structs comme objets et on les fait passer par des pointeurs :

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

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

Pour obtenir des choses comme l'héritage et le polymorphisme, il faut travailler un peu plus dur. Vous pouvez faire de l'héritage manuel en faisant en sorte que le premier membre d'une structure soit une instance de la superclasse, et vous pouvez ensuite faire circuler librement les pointeurs vers les classes de base et dérivées :

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

Pour obtenir le polymorphisme (c'est-à-dire les fonctions virtuelles), vous utilisez des pointeurs de fonction, et éventuellement des tables de pointeurs de fonction, également connues sous le nom de tables virtuelles ou vtables :

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

Et c'est comme ça qu'on fait du polymorphisme en C. Ce n'est pas joli, mais ça fait l'affaire. Il y a quelques problèmes épineux impliquant les casts de pointeurs entre les classes de base et les classes dérivées, qui sont sûrs tant que la classe de base est le premier membre de la classe dérivée. L'héritage multiple est beaucoup plus difficile - dans ce cas, pour passer d'une classe de base à une autre que la première, vous devez ajuster manuellement vos pointeurs en fonction des décalages appropriés, ce qui est vraiment délicat et source d'erreurs.

Une autre chose (délicate) que vous pouvez faire est de changer le type dynamique d'un objet au moment de l'exécution ! Il suffit de lui réassigner un nouveau pointeur de table virtuelle. Vous pouvez même changer sélectivement certaines fonctions virtuelles tout en conservant d'autres, créant ainsi de nouveaux types hybrides. Faites juste attention à créer une nouvelle table virtuelle au lieu de modifier la table virtuelle globale, sinon vous affecterez accidentellement tous les objets d'un type donné.

6 votes

Adam, le plaisir de changer la vtable globale d'un type est de simuler le typage en canard en C. :)

0 votes

Maintenant je plains le C++... Bon bien sûr la syntaxe C++ est plus claire, mais comme ce n'est pas une syntaxe triviale, je suis mitigé. Je me demande si quelque chose d'hybride entre C++ et C pourrait être réalisé, de sorte que void* serait toujours un type castable valide. La partie avec struct derived {struct base super;}; Il est évident de deviner comment cela fonctionne, puisque par l'ordre des octets, c'est correct.

3 votes

+1 pour un code élégant, bien écrit. C'est exactement ce que je recherchais !

34voto

Kieveli Points 7162

(arraché à Comment écrire du code C de production ? [fermé] )

J'ai travaillé une fois avec une bibliothèque C qui était implémentée d'une manière qui m'a semblé très élégante. Ils avaient écrit, en C, un moyen de définir des objets, puis d'en hériter de manière à ce qu'ils soient aussi extensibles qu'un objet C++. L'idée de base était la suivante :

  • Chaque objet avait son propre fichier
  • Les fonctions et variables publiques sont définies dans le fichier .h d'un objet.
  • Les variables et fonctions privées se trouvaient uniquement dans le fichier .c.
  • Pour "hériter", on crée une nouvelle structure dont le premier membre est l'objet dont on veut hériter.

L'héritage est difficile à décrire, mais en gros, c'était ça :

struct vehicle {
   int power;
   int weight;
}

Puis dans un autre fichier :

struct van {
   struct vehicle base;
   int cubic_size;
}

Vous pourriez alors avoir un van créé en mémoire, et utilisé par un code qui ne connaît que les véhicules :

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

Cela a fonctionné à merveille, et les fichiers .h définissaient exactement ce que l'on devait pouvoir faire avec chaque objet.

0 votes

J'aime beaucoup cette solution, sauf que tous les éléments internes de l'"objet" sont publics.

6 votes

@Software Monkey : C n'a pas de contrôle d'accès. La seule façon de cacher les détails de l'implémentation est d'interagir par le biais de pointeurs opaques, ce qui peut être assez pénible, puisque tous les champs devraient être accessibles par des méthodes d'accès qui ne peuvent probablement pas être inlined.

1 votes

@Adam : Les compilateurs supportant les optimisations au moment de la liaison les mettront en ligne sans problème...

34voto

philant Points 17345

Système d'objets C (COS) semble prometteur (il est encore en version alpha). Il essaie de garder au minimum les concepts disponibles pour des raisons de simplicité et de flexibilité : programmation orientée objet uniforme incluant les classes ouvertes, les métaclasses, les métaclasses de propriétés, les génériques, les multiméthodes, la délégation, la propriété, les exceptions, les contrats et les fermetures. Il existe un projet de document (PDF) qui le décrit.

Exception en C est une implémentation en C89 du TRY-CATCH-FINALLY que l'on trouve dans d'autres langages OO. Il est fourni avec une suite de tests et quelques exemples.

Les deux par Laurent Deniau, qui travaille beaucoup sur La POO en C .

0 votes

@vonbrand COS a migré sur github où le dernier commit date de l'été dernier. La maturité peut expliquer l'absence de commit.

18voto

James Cape Points 482

Le bureau GNOME pour Linux est écrit en C orienté objet, et il possède un modèle d'objet appelé " Objet GO "qui prend en charge les propriétés, l'héritage, le polymorphisme, ainsi que d'autres fonctionnalités telles que les références, la gestion des événements (appelés "signaux"), le typage de l'exécution, les données privées, etc.

Il comprend des astuces de préprocesseur permettant de faire des choses comme le typecasting dans la hiérarchie des classes, etc. Voici un exemple de classe que j'ai écrit pour GNOME (des choses comme gchar sont des typedefs) :

Source de la classe

En-tête de classe

Dans la structure GObject, il y a un entier GType qui est utilisé comme un numéro magique pour le système de typage dynamique de GLib (vous pouvez convertir la structure entière en un "GType" pour trouver son type).

0 votes

Malheureusement, le fichier read me/tutorial (lien wiki) ne fonctionne pas et il n'y a qu'un manuel de référence pour cela (je parle de GObject et non de GTK). merci de fournir des fichiers tutoriels pour la même chose ...

0 votes

Les liens ont été corrigés.

5 votes

Les liens sont à nouveau cassés.

7voto

zebrabox Points 3937

Légèrement hors sujet, mais le premier compilateur C++, c-front, compilait le C++ en C, puis en assembleur.
Préservé ici

0 votes

Je l'ai déjà vu auparavant. Je crois que c'était un beau travail.

0 votes

@Anthony Cuozzo : Stan Lippman a écrit un excellent livre intitulé 'C++ - Inside the object model' dans lequel il relate beaucoup de ses expériences et décisions de conception dans l'écriture et la maintenance de c-front. C'est toujours un bon livre et il m'a énormément aidé lors de la transition du C au C++ il y a plusieurs années.

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