OO best practices for C programmes [fermé]


19

"Si vous voulez vraiment du sucre OO - allez utiliser C ++" - a été la réponse immédiate d'un de mes amis quand j'ai demandé cela. Je sais que deux choses sont complètement fausses ici. Le premier OO n'est PAS du «sucre» et le second, le C ++ n'a PAS absorbé le C.

Nous devons écrire un serveur en C (dont le front-end sera en Python), et j'explore donc de meilleures façons de gérer de gros programmes C.

La modélisation d'un grand système en termes d'objets et d'interactions d'objets le rend plus gérable, maintenable et extensible. Mais lorsque vous essayez de traduire ce modèle en C qui ne porte pas d'objets (et tout le reste), vous êtes confronté à des décisions importantes.

Créez-vous une bibliothèque personnalisée pour fournir les abstractions OO dont votre système a besoin? Des choses comme les objets, l'encapsulation, l'héritage, le polymorphisme, les exceptions, pub / sub (événements / signaux), les espaces de noms, l'introspection, etc. (par exemple GObject ou COS ).

Ou, vous utilisez simplement les constructions C de base ( structet les fonctions) pour approximer toutes vos classes d'objets (et autres abstractions) de manière ad hoc. (par exemple, certaines des réponses à cette question sur SO )

La première approche vous donne un moyen structuré d'implémenter l'intégralité de votre modèle en C. Mais cela ajoute également une couche de complexité que vous devez maintenir. (Rappelez-vous, la complexité était ce que nous voulions réduire en utilisant des objets en premier lieu).

Je ne connais pas la deuxième approche et son efficacité pour approximer toutes les abstractions dont vous pourriez avoir besoin.

Donc, mes questions simples sont: Quelles sont les meilleures pratiques pour réaliser une conception orientée objet en C. N'oubliez pas que je ne demande pas COMMENT le faire. Ceci et ces questions en parlent, et il y a même un livre à ce sujet. Ce qui m'intéresse le plus, ce sont des conseils / exemples réalistes qui abordent les vrais problèmes qui se présentent lorsque dong ceci.

Remarque: veuillez ne pas conseiller pourquoi C ne devrait pas être utilisé en faveur de C ++. Nous avons bien dépassé cette étape.


3
Vous pouvez écrire un serveur C ++ afin que son interface externe soit extern "C"et puisse être utilisée à partir de python. Vous pouvez le faire manuellement ou demander à SWIG de vous aider. Donc, le désir de frontend python n'est pas une raison pour ne pas utiliser C ++. Cela ne veut pas dire qu'il n'y a aucune raison valable de vouloir rester avec C.
Jan Hudec

1
Cette question doit être clarifiée. Actuellement, les paragraphes 4 et 5 demandent essentiellement quelle approche adopter, mais vous dites ensuite que vous "ne demandez pas COMMENT le faire" et que vous souhaitez plutôt (une liste?) Des meilleures pratiques. Si vous ne cherchez pas COMMENT le faire en C, demandez-vous alors une liste des "meilleures pratiques" liées à la POO en général? Si c'est le cas, dites-le, mais sachez que la question sera probablement fermée pour être subjective .
Caleb

:) Je demande de vrais exemples (code ou autre) où cela a été fait - et les problèmes qu'ils ont rencontrés en le faisant.
treecoder

4
Vos exigences semblent déroutantes. Vous insistez sur l'utilisation de l'orientation objet sans raison que je puisse voir (dans certains langages, c'est une façon de rendre les programmes plus maintenables, mais pas en C), et insistez sur l'utilisation de C. L'orientation objet est un moyen, pas une fin ou une panacée . De plus, il bénéficie grandement du support linguistique. Si vous vouliez réellement OO, vous auriez dû en tenir compte lors de la sélection de la langue. Une question sur la façon de créer un grand système logiciel avec C aurait beaucoup plus de sens.
David Thornley

Vous voudrez peut-être jeter un œil à "Modélisation et conception orientées objet". (Rumbaugh et al.): Il y a une section sur la mise en correspondance des conceptions OO avec des langages comme C.
Giorgio

Réponses:


16

De ma réponse à Comment structurer des projets complexes en C (pas OO mais sur la gestion de la complexité en C):

La clé est la modularité. Ceci est plus facile à concevoir, implémenter, compiler et maintenir.

  • Identifiez les modules dans votre application, comme les classes dans une application OO.
  • Interface et implémentation séparées pour chaque module, ne mettez en interface que ce dont les autres modules ont besoin. N'oubliez pas qu'il n'y a pas d'espace de noms en C, vous devez donc rendre tout dans vos interfaces unique (par exemple, avec un préfixe).
  • Masquez les variables globales dans l'implémentation et utilisez les fonctions d'accesseur pour la lecture / écriture.
  • Ne pensez pas en termes d'héritage, mais en termes de composition. En règle générale, n'essayez pas d'imiter C ++ en C, ce serait très difficile à lire et à maintenir.

De ma réponse à Quelles sont les conventions de dénomination typiques pour les fonctions publiques et privées OO C (je pense que c'est une meilleure pratique):

La convention que j'utilise est:

  • Fonction publique (dans le fichier d'en-tête):

    struct Classname;
    Classname_functionname(struct Classname * me, other args...);
  • Fonction privée (statique dans le fichier d'implémentation)

    static functionname(struct Classname * me, other args...)

De plus, de nombreux outils UML sont capables de générer du code C à partir de diagrammes UML. Un open source est Topcased .



1
Très bonne réponse. Visez la modularité. OO est censé le fournir, mais 1) dans la pratique, il est trop courant de se retrouver avec des spaghettis OO et 2) ce n'est pas le seul moyen. Pour quelques exemples dans la vie réelle, regardez le noyau linux (modularité de style C) et les projets qui utilisent glib (O-style O). J'ai eu l'occasion de travailler avec les deux styles, et la modularité de style C de l'OMI l'emporte.
Joh

Et pourquoi exactement la composition est une meilleure approche que l'héritage? La justification et les références à l'appui sont les bienvenues. Ou vous parliez uniquement des programmes C?
Aleksandr Blekh

1
@AleksandrBlekh - Oui, je parle uniquement de C.
mouviciel

16

Je pense que vous devez différencier OO et C ++ dans cette discussion.

L'implémentation d'objets est possible en C, et c'est assez simple - il suffit de créer des structures avec des pointeurs de fonction. C'est votre "deuxième approche", et j'irais avec elle. Une autre option consisterait à ne pas utiliser de pointeurs de fonction dans la structure, mais plutôt à transmettre la structure de données aux fonctions lors d'appels directs, en tant que pointeur "contextuel". C'est mieux, à mon humble avis, car il est plus lisible, plus facilement traçable et permet d'ajouter des fonctions sans changer la structure (facile sur l'héritage si aucune donnée n'est ajoutée). C'est en fait ainsi que C ++ implémente généralement lethis pointeur.

Le polymorphisme devient plus compliqué car il n'y a pas de prise en charge intégrée de l'héritage et de l'abstraction, vous devrez donc soit inclure la structure parent dans votre classe enfant, soit faire beaucoup de copier-coller, l'une ou l'autre de ces options est franchement horrible, même si techniquement Facile. Énorme quantité de bugs attendant de se produire.

Les fonctions virtuelles peuvent facilement être obtenues grâce à des pointeurs de fonction pointant vers différentes fonctions selon les besoins, encore une fois - très sujets aux bogues lorsqu'ils sont effectués manuellement, beaucoup de travail fastidieux pour initialiser correctement ces pointeurs.

En ce qui concerne les espaces de noms, les exceptions, les modèles, etc. - je pense que si vous êtes limité à C - vous devriez simplement y renoncer. J'ai travaillé sur l'écriture d'OO en C, je ne le ferais pas si j'avais le choix (sur ce lieu de travail, je me suis littéralement battu pour introduire le C ++, et les gestionnaires ont été "surpris" de la facilité avec laquelle il était possible de l'intégrer). avec le reste des modules C à la fin.).

Cela va sans dire que si vous pouvez utiliser C ++ - utilisez C ++. Il n'y a aucune vraie raison de ne pas le faire.


En fait, vous pouvez hériter d'une structure et ajouter des données: déclarez simplement le premier élément de la structure enfant en tant que variable dont le type est la structure parent. Ensuite, lancez selon vos besoins.
mouviciel

1
@mouviciel - oui. J'ai dit ça. " ... vous devrez donc inclure la structure parent dans votre classe enfant, ou ... "
littleadv

5
Aucune raison d'essayer d'implémenter l'héritage. Comme moyen de réutiliser le code, c'est une idée erronée pour commencer. La composition des objets est plus facile et meilleure.
KaptajnKold

@KaptajnKold - d'accord.
littleadv

8

Voici les bases de la création d'une orientation d'objet en C

1. Création d'objets et encapsulation

Habituellement - on crée un objet comme

object_instance = create_object_typex(parameter);

Les méthodes peuvent être définies de l'une des deux manières ici.

object_type_method_function(object_instance,parameter1)
OR
object_instance->method_function(object_instance_private_data,parameter1)

Notez que dans la plupart des cas, object_instance (or object_instance_private_data)est retourné est de typevoid *. L'application ne peut pas référencer des membres individuels ou des fonctions de celui-ci.

De plus, chaque méthode utilise ces object_instance pour la méthode suivante.

2. Polymorphisme

Nous pouvons utiliser de nombreuses fonctions et pointeurs de fonction pour remplacer certaines fonctionnalités lors de l'exécution.

par exemple - toutes les méthodes object_methods sont définies comme un pointeur de fonction qui peut être étendu aux méthodes publiques et privées.

Nous pouvons également appliquer une surcharge de fonctions dans un sens limité, en utilisant var_args ce qui est très similaire à la façon dont le nombre variable d'arguments est défini dans printf. oui, ce n'est pas aussi flexible en C ++ - mais c'est la manière la plus proche.

3. Définition de l'héritage

La définition de l'héritage est un peu délicate mais on peut faire ce qui suit avec les structures.

typedef struct { 
     int age,
     int sex,
} person; 

typedef struct { 
     person p,
     enum specialty s;
} doctor;

typedef struct { 
     person p,
     enum subject s;
} engineer;

// use it like
engineer e1 = create_engineer(); 
get_person_age( (person *)e1); 

ici, le doctoret engineerdérive de la personne et il est possible de le transcrire à un niveau supérieur, par exemple person.

Le meilleur exemple de ceci est utilisé dans GObject et ses objets dérivés.

4. Création de classes virtuelles Je cite un exemple concret d'une bibliothèque appelée libjpeg utilisée par tous les navigateurs pour le décodage jpeg. Il crée une classe virtuelle appelée error_manager que l'application peut créer une instance concrète et fournir en retour -

struct djpeg_dest_struct {
  /* start_output is called after jpeg_start_decompress finishes.
   * The color map will be ready at this time, if one is needed.
   */
  JMETHOD(void, start_output, (j_decompress_ptr cinfo,
                               djpeg_dest_ptr dinfo));
  /* Emit the specified number of pixel rows from the buffer. */
  JMETHOD(void, put_pixel_rows, (j_decompress_ptr cinfo,
                                 djpeg_dest_ptr dinfo,
                                 JDIMENSION rows_supplied));
  /* Finish up at the end of the image. */
  JMETHOD(void, finish_output, (j_decompress_ptr cinfo,
                                djpeg_dest_ptr dinfo));

  /* Target file spec; filled in by djpeg.c after object is created. */
  FILE * output_file;

  /* Output pixel-row buffer.  Created by module init or start_output.
   * Width is cinfo->output_width * cinfo->output_components;
   * height is buffer_height.
   */
  JSAMPARRAY buffer;
  JDIMENSION buffer_height;
};

Notez ici que JMETHOD se développe dans un pointeur de fonction via une macro qui doit être chargée avec les bonnes méthodes respectivement.


J'ai essayé de dire tant de choses sans trop d'explications individuelles. Mais j'espère que les gens pourront essayer leurs propres choses. Cependant, mon intention est juste de montrer comment les choses évoluent.

De plus, il y aura de nombreux arguments selon lesquels ce ne sera pas exactement la vraie propriété de l'équivalent C ++. Je sais que OO en C ne sera pas aussi strict à sa définition. Mais en travaillant comme ça, on comprendrait certains des principes fondamentaux.

L'important n'est pas que OO soit aussi strict qu'en C ++ et JAVA. C'est que l'on peut organiser structurellement le code en pensant à OO et le faire fonctionner de cette façon.

Je recommanderais fortement aux gens de voir la vraie conception de libjpeg et les ressources suivantes

une. Programmation orientée objet en C
b. c'est un bon endroit où les gens échangent des idées
c. et voici le livre complet


3

L'orientation des objets se résume à trois choses:

1) Conception de programme modulaire avec classes autonomes.

2) Protection des données avec encapsulation privée.

3) Héritage / polymorphisme et diverses autres syntaxes utiles comme les constructeurs / destructeurs, les modèles, etc.

1 est de loin le plus important, et il est également complètement indépendant de la langue, il s'agit de conception de programme. En C, vous faites cela en créant des "modules de code" autonomes composés d'un fichier .h et d'un fichier .c. Considérez cela comme l'équivalent d'une classe OO. Vous pouvez décider de ce qui doit être placé à l'intérieur de ce module par le bon sens, UML ou toute autre méthode de conception OO que vous utilisez pour les programmes C ++.

2 est également très important, non seulement pour protéger un accès intentionnel aux données privées, mais aussi pour se protéger contre un accès involontaire, c'est-à-dire "l'encombrement de l'espace de noms". C ++ le fait de manière plus élégante que C, mais cela peut toujours être réalisé en C en utilisant le mot-clé statique. Toutes les variables que vous auriez déclarées privées dans une classe C ++ devraient être déclarées statiques en C et placées à la portée du fichier. Ils ne sont accessibles qu'à partir de leur propre module de code (classe). Vous pouvez écrire "setters / getters" comme vous le feriez en C ++.

3 est utile mais pas nécessaire. Vous pouvez écrire des programmes OO sans héritage ou sans constructeurs / destructeurs. Ces choses sont agréables à avoir, elles peuvent certainement rendre les programmes plus élégants et peut-être aussi plus sûrs (ou l'inverse s'ils sont utilisés avec négligence). Mais ils ne sont pas nécessaires. Étant donné que C ne prend en charge aucune de ces fonctionnalités utiles, vous devrez simplement vous en passer. Les constructeurs peuvent être remplacés par des fonctions init / destruct.

L'héritage peut être fait à travers diverses astuces de structure, mais je le déconseillerais, car cela rendra probablement votre programme plus complexe sans aucun gain (l'héritage en général devrait être soigneusement appliqué, non seulement en C mais dans n'importe quelle langue).

Enfin, chaque astuce OO peut être effectuée dans le livre de C. Axel-Tobias Schreiner "Programmation orientée objet avec ANSI C" du début des années 90 le prouve. Cependant, je ne recommanderais ce livre à personne: il ajoute à vos programmes C une complexité désagréable et étrange qui ne vaut tout simplement pas la peine. (Le livre est disponible gratuitement ici pour ceux qui sont toujours intéressés malgré mon avertissement.)

Donc, mon conseil est d'implémenter 1) et 2) ci-dessus et de sauter le reste. C'est une façon d'écrire des programmes C qui a fait ses preuves depuis plus de 20 ans.


2

Emprunter une certaine expérience des différents temps d'exécution d'Objective-C, écrire une capacité OO polymorphe dynamique en C n'est pas trop difficile (la rendre rapide et facile à utiliser, en revanche, semble toujours en cours après 25 ans). Cependant, si vous implémentez une capacité d'objet de style Objective-C sans étendre la syntaxe du langage, le code avec lequel vous vous retrouvez est assez compliqué:

  • chaque classe est définie par une structure déclarant sa superclasse, les interfaces auxquelles elle se conforme, les messages qu'elle implémente (comme une carte de "sélecteur", le nom du message, à "implémentation", la fonction qui fournit le comportement) et l'instance de la classe disposition variable.
  • chaque instance est définie par une structure qui contient un pointeur sur sa classe, puis ses variables d'instance.
  • l'envoi de messages est implémenté (donner ou prendre des cas particuliers) en utilisant une fonction qui ressemble objc_msgSend(object, selector, …). En sachant de quelle classe l'objet est une instance, il peut trouver l'implémentation correspondant au sélecteur et ainsi exécuter la fonction correcte.

Cela fait partie d'une bibliothèque OO à usage général conçue pour permettre à plusieurs développeurs d'utiliser et d'étendre les classes les uns des autres, donc pourrait être exagéré pour votre propre projet. J'ai souvent conçu des projets C comme des projets orientés classes "statiques" utilisant des structures et des fonctions: - chaque classe est la définition d'une structure C spécifiant la disposition ivar - chaque instance est juste une instance de la structure correspondante - les objets ne peuvent pas être "messaged", mais des fonctions de type méthode ressemblantMyClass_doSomething(struct MyClass *object, …) sont définies. Cela rend les choses plus claires dans le code que l'approche ObjC mais a moins de flexibilité.

Le choix des compromis dépend de votre propre projet: il semble que les autres programmeurs n'utilisent pas vos interfaces C, le choix se résume donc aux préférences internes. Bien sûr, si vous décidez de vouloir quelque chose comme la bibliothèque d'exécution objc, il existe des bibliothèques d'exécution objc multiplateforme qui fonctionneront.


1

GObject ne cache pas vraiment de complexité et introduit sa propre complexité. Je dirais que la chose ad-hoc est plus facile que GObject à moins que vous n'ayez besoin des choses avancées de GObject comme les signaux ou la machinerie d'interface.

La chose est légèrement différente avec COS car cela vient avec un préprocesseur qui étend la syntaxe C avec certaines constructions OO. Il existe un préprocesseur similaire pour GObject, le G Object Builder .

Vous pouvez également essayer le langage de programmation Vala , qui est un langage complet de haut niveau qui se compile en C et d'une manière qui permet d'utiliser les bibliothèques Vala à partir de code C simple. Il peut utiliser soit GObject, son propre cadre d'objet ou la manière ad-hoc (avec des fonctionnalités limitées).


1

Tout d'abord, à moins qu'il ne s'agisse de devoirs ou qu'il n'y ait pas de compilateur C ++ pour le périphérique cible, je ne pense pas que vous ayez à utiliser C, vous pouvez fournir une interface C avec une liaison C à partir de C ++ assez facilement.

Deuxièmement, j'examinerais à quel point vous vous reposerez sur le polymorphisme et les exceptions et les autres fonctionnalités que les cadres peuvent fournir, si ce n'est pas beaucoup de structures simples avec des fonctions connexes seront beaucoup plus faciles qu'un cadre complet, si une partie importante de votre le design en a besoin, puis mordez la balle et utilisez un cadre pour que vous n'ayez pas à implémenter les fonctionnalités vous-même.

Si vous n'avez pas encore vraiment de conception pour prendre la décision, faites un pic et voyez ce que le code vous dit.

enfin, il ne doit pas nécessairement être un choix ou un autre (bien que si vous utilisez le framework depuis le début, vous pouvez aussi bien le conserver), il devrait être possible de commencer avec des structures simples pour les parties simples et d'ajouter uniquement des bibliothèques comme requis.

EDIT: C'est donc une décision prise par la direction de droite permet d'ignorer le premier point.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.