146 votes

Comment implémenter une classe en C ?

En supposant que je doive utiliser le langage C (pas de compilateur C++ ou orienté objet) et que je ne dispose pas d'allocation dynamique de mémoire, quelles sont les techniques que je peux utiliser pour implémenter une classe, ou une bonne approximation d'une classe ? Est-ce toujours une bonne idée d'isoler la "classe" dans un fichier séparé ? Supposons que nous puissions préallouer la mémoire en supposant un nombre fixe d'instances, ou même en définissant la référence à chaque objet comme une constante avant la compilation. N'hésitez pas à faire des hypothèses sur le concept de POO que je devrai mettre en œuvre (cela variera) et à suggérer la meilleure méthode pour chacun d'entre eux.

Restrictions :

  • Je dois utiliser le C et non un OOP parce que j'écris du code pour un système embarqué, et le compilateur et la base de code préexistante est en C.
  • Il n'y a pas d'allocation dynamique de la mémoire car nous n'avons pas assez de mémoire pour supposer raisonnablement que nous n'en manquerons pas si nous commençons à l'allouer dynamiquement la mémoire.
  • Les compilateurs avec lesquels nous travaillons n'ont pas de problèmes avec les pointeurs de fonction.

29 votes

Question obligatoire : Devez-vous écrire du code orienté objet ? Si c'est le cas pour une raison ou une autre, c'est très bien, mais vous allez devoir mener une bataille plutôt difficile. C'est probablement mieux si vous évitez d'essayer d'écrire du code orienté objet en C. C'est certainement possible - voir l'excellente réponse de unwind - mais ce n'est pas exactement "facile", et si vous travaillez sur un système embarqué avec une mémoire limitée, ce n'est peut-être pas faisable. Je peux me tromper, cependant - je n'essaie pas de vous convaincre de ne pas le faire, juste de présenter des contrepoints qui n'ont peut-être pas été présentés.

1 votes

À proprement parler, nous ne sommes pas obligés de le faire. Cependant, la complexité du système a rendu le code impossible à maintenir. Mon sentiment est que la meilleure façon de réduire la complexité est d'implémenter certains concepts OOP. Merci à tous ceux qui ont répondu dans les 3 minutes. Vous êtes fous et rapides !

8 votes

C'est juste mon humble opinion, mais la POO ne rend pas le code instantanément maintenable. Elle peut le rendre plus facile à gérer, mais pas nécessairement plus facile à maintenir. Vous pouvez avoir des "espaces de noms" en C (l'Apache Portable Runtime préfixe tous les symboles globaux avec le symbole apr_ et GLib les préfixe avec g_ pour créer un espace de noms) et d'autres facteurs d'organisation sans POO. Si vous devez restructurer l'application de toute façon, j'envisagerais de passer un peu de temps à essayer de trouver une structure procédurale plus facile à maintenir.

92voto

unwind Points 181987

Cela dépend de l'ensemble exact de fonctionnalités "orientées objet" que vous souhaitez avoir. Si vous avez besoin de choses comme la surcharge et/ou les méthodes virtuelles, vous devez probablement inclure les pointeurs de fonction dans les structures :

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

Cela vous permet d'implémenter une classe, en "héritant" de la classe de base, et en implémentant une fonction appropriée :

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

Cela nécessite bien sûr l'implémentation d'un constructeur, qui s'assure que le pointeur de fonction est correctement configuré. Normalement, vous devriez allouer dynamiquement de la mémoire pour l'instance, mais vous pouvez également laisser l'appelant le faire :

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

Si vous voulez plusieurs constructeurs différents, vous devrez "décorer" les noms des fonctions, vous ne pouvez pas avoir plus d'une fonction. rectangle_new() fonction :

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

Voici un exemple de base montrant l'utilisation :

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

J'espère que cela vous donne quelques idées, au moins. Pour un cadre de travail orienté objet riche et réussi en C, regardez dans glib's Objet GO bibliothèque.

Notez également qu'il n'y a pas de "classe" explicite modélisée ci-dessus, chaque objet possède ses propres pointeurs de méthode, ce qui est un peu plus flexible que ce que l'on trouve généralement en C++. De plus, cela coûte de la mémoire. Vous pourriez éviter cela en plaçant les pointeurs de méthode dans un fichier class et inventer un moyen pour chaque instance d'objet de référencer une classe.

0 votes

N'ayant pas eu à essayer d'écrire du C orienté objet, est-il généralement préférable de faire des fonctions qui prennent const ShapeClass * o const void * comme arguments ? Il semblerait que la seconde solution soit un peu plus agréable pour l'héritage, mais je peux voir des arguments dans les deux sens...

1 votes

@Chris : Oui, c'est une question difficile :| GTK+ (qui utilise GObject) utilise la classe appropriée, c'est-à-dire RectangleClass *. Cela signifie que vous devez souvent faire des castings, mais ils fournissent des macros pratiques pour vous aider, ainsi vous pouvez toujours faire un cast de BASECLASS *p vers SUBCLASS * en utilisant simplement SUBCLASS(p).

1 votes

Mon compilateur échoue sur la deuxième ligne de code : float (*computeArea)(const ShapeClass *shape); en disant que ShapeClass est d'un type inconnu.

26voto

erelender Points 3634

J'ai dû le faire une fois aussi pour un devoir. J'ai suivi cette approche :

  1. Définissez vos membres de données dans un struct.
  2. Définissez les membres de votre fonction qui prennent un pointeur sur votre structure comme premier argument.
  3. Faites-les en une tête et un c. En-tête pour la définition de la structure et déclarations de fonctions, c pour les implémentations.

Un exemple simple serait le suivant :

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///

0 votes

C'est ce que j'ai fait dans le passé, mais en ajoutant la simulation de la portée en plaçant les prototypes de fonction dans le fichier .c ou .h selon les besoins (comme je l'ai mentionné dans ma réponse).

0 votes

J'aime ça, la déclaration de la structure alloue toute la mémoire. Pour une raison quelconque, j'avais oublié que cela fonctionnerait bien.

0 votes

Je pense que vous avez besoin d'un typedef struct Queue Queue; là-dedans.

12voto

Pete Kirkham Points 32484

Si vous ne voulez qu'une seule classe, utilisez un tableau de struct en tant que données "objets" et passer des pointeurs vers eux aux fonctions "membres". Vous pouvez utiliser typedef struct _whatever Whatever avant de déclarer struct _whatever pour cacher l'implémentation au code client. Il n'y a aucune différence entre un tel "objet" et la bibliothèque standard de C FILE objet.

Si vous voulez plus d'une classe avec héritage et fonctions virtuelles, il est courant d'avoir des pointeurs vers les fonctions comme membres de la structure, ou un pointeur partagé vers une table de fonctions virtuelles. Le site Objet GO utilise à la fois cette astuce et celle du typedef, et est largement utilisée.

Il existe également un livre sur les techniques de ce type disponible en ligne. Programmation orientée objet avec ANSI C .

1 votes

Cool ! D'autres recommandations de livres sur la POO en C ? Ou toute autre technique de conception moderne en C ? (ou les systèmes embarqués ?)

7voto

Alex Points 195

Vous pouvez jeter un coup d'oeil à GOBject. Il s'agit d'une bibliothèque OS qui vous donne une manière verbeuse de faire un objet.

http://library.gnome.org/devel/gobject/stable/

1 votes

Très intéressé. Quelqu'un sait-il ce qu'il en est des licences ? Dans le cadre de mon travail, l'intégration d'une bibliothèque open source dans un projet ne sera probablement pas possible d'un point de vue juridique.

0 votes

GTK+, et toutes les bibliothèques qui font partie de ce projet (y compris GObject), sont sous licence GNU LGPL, ce qui signifie que vous pouvez les lier à partir de logiciels propriétaires. Je ne sais pas si c'est faisable pour le travail embarqué, cependant.

6voto

Frank Grimm Points 939

Miro Samek a développé un framework C orienté objet pour son framework de machine à état : http://sourceforge.net/projects/qpc/ . Et il a aussi écrit un livre à ce sujet : http://www.state-machine.com/psicc2/ .

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