Quel serait un ensemble de hacks de préprocesseur astucieux (compatibles ANSI C89 / ISO C90) qui permettent une sorte d'orientation objet laide (mais utilisable) en C?
Je connais quelques langages orientés objet différents, alors ne répondez pas avec des réponses telles que "Apprendre C ++!". J'ai lu " Programmation orientée objet avec ANSI C " (attention: format PDF ) et plusieurs autres solutions intéressantes, mais je m'intéresse surtout à la vôtre :-)!
Réponses:
C Object System (COS) semble prometteur (il est toujours en version alpha). Il essaie de minimiser les concepts disponibles dans un souci de simplicité et de flexibilité: programmation orientée objet uniforme comprenant des classes ouvertes, des métaclasses, des métaclasses de propriétés, des génériques, des méthodes multiples, la délégation, la propriété, les exceptions, les contrats et les fermetures. Il existe un projet de papier (PDF) qui le décrit.
L'exception en C est une implémentation C89 de TRY-CATCH-FINALLY trouvée dans d'autres langages OO. Il est livré avec une suite de tests et quelques exemples.
À la fois par Laurent Deniau, qui travaille beaucoup sur POO en C .
la source
Je déconseillerais l'utilisation du préprocesseur (ab) pour essayer de rendre la syntaxe C plus semblable à celle d'un autre langage plus orienté objet. Au niveau le plus élémentaire, vous utilisez simplement des structures simples comme objets et les passez par des pointeurs:
Pour obtenir des choses comme l'héritage et le polymorphisme, vous devez travailler un peu plus dur. Vous pouvez faire l'héritage manuel en faisant en sorte que le premier membre d'une structure soit une instance de la superclasse, puis vous pouvez convertir librement des pointeurs vers les classes de base et dérivées:
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 appelées tables virtuelles ou vtables:
Et c'est comme ça que vous faites le polymorphisme en C. Ce n'est pas joli, mais ça fait le travail. Il y a des problèmes persistants impliquant des casts de pointeurs entre les classes de base et 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 faire le cas entre des classes de base autres que la première, vous devez ajuster manuellement vos pointeurs en fonction des décalages appropriés, ce qui est vraiment délicat et sujet aux 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! Vous venez de lui réaffecter un nouveau pointeur vtable. Vous pouvez même modifier sélectivement certaines des fonctions virtuelles tout en en conservant d'autres, créant de nouveaux types hybrides. Veillez simplement à créer une nouvelle vtable au lieu de modifier la vtable globale, sinon vous affecterez accidentellement tous les objets d'un type donné.
la source
struct derived {struct base super;};
est évidente pour deviner comment cela fonctionne, car dans l'ordre des octets, c'est correct.Une fois, j'ai travaillé avec une bibliothèque C qui a été implémentée d'une manière qui m'a paru assez élégante. Ils avaient écrit, en C, une façon de définir des objets, puis en héritent pour qu'ils soient aussi extensibles qu'un objet C ++. L'idée de base était la suivante:
L'héritage est difficile à décrire, mais c'était essentiellement ceci:
Puis dans un autre fichier:
Ensuite, vous pourriez avoir une camionnette créée en mémoire et utilisée par un code qui ne connaissait que les véhicules:
Cela fonctionnait à merveille et les fichiers .h définissaient exactement ce que vous devriez pouvoir faire avec chaque objet.
la source
Le bureau GNOME pour Linux est écrit en C orienté objet, et il a un modèle objet appelé " GObject " qui prend en charge les propriétés, l'héritage, le polymorphisme, ainsi que d'autres goodies comme les références, la gestion des événements (appelés "signaux"), le runtime saisie, données privées, etc.
Il inclut des hacks de préprocesseur pour faire des choses comme le typage 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 classe
En-tête de classe
Dans la structure GObject, il y a un entier GType qui est utilisé comme nombre magique pour le système de typage dynamique de GLib (vous pouvez convertir la structure entière en un "GType" pour trouver son type).
la source
J'avais l'habitude de faire ce genre de chose en C, avant de savoir ce qu'était la POO.
Voici un exemple, qui implémente un tampon de données qui se développe à la demande, étant donné une taille minimale, un incrément et 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, pas seulement un tampon d'octets de longueur variable.
L'idée est que l'objet est instancié à l'aide de xxx_crt () et supprimé à l'aide de xxx_dlt (). Chacune des méthodes "membres" utilise un pointeur spécifiquement typé pour fonctionner.
J'ai implémenté une liste chaîné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 de mettre en œuvre l'héritage avec cette approche. J'imagine qu'un mélange de celui offert par Kieveli pourrait être une bonne voie.
dtb.c:
dtb.h
PS: vint était simplement un typedef de int - je l'ai utilisé pour me rappeler que sa longueur était variable d'une plateforme à l'autre (pour le portage).
la source
Légèrement hors sujet, mais le compilateur C ++ d'origine, Cfront , a compilé C ++ en C puis en assembleur.
Conservé ici .
la source
Si vous pensez aux méthodes appelées sur des objets comme des méthodes statiques qui passent un 'implicite'
this
' dans la fonction, cela peut faciliter la réflexion OO en C.Par exemple:
devient:
Ou quelque chose comme ça.
la source
string->length(s);
ffmpeg (une boîte à outils pour le traitement vidéo) est écrit en C pur (et en langage d'assemblage), mais en utilisant un style orienté objet. Il est plein de structures avec des pointeurs de fonction. Il existe un ensemble de fonctions d'usine qui initialisent les structures avec les pointeurs de "méthode" appropriés.
la source
Si vous pense vraiment catefully, même l' utilisation de la bibliothèque standard C OOP - considèrent
FILE *
comme un exemple:fopen()
initialise unFILE *
objet, et vous l' utilisez utiliser des méthodes membresfscanf()
,fprintf()
,fread()
,fwrite()
et d' autres, et éventuellement mettre la dernière main avecfclose()
.Vous pouvez également utiliser la méthode pseudo-Objective-C qui n'est pas difficile non plus:
Utiliser:
C'est ce qui peut résulter d'un code Objective-C comme celui-ci, si un assez vieux traducteur Objective-C-to-C est utilisé:
la source
__attribute__((constructor))
-onvoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
renvoie également aFILE *
pour un autre exemple.Je pense que ce qu'Adam Rosenfield a posté est la manière correcte de faire la POO en C. Je voudrais ajouter que ce qu'il montre est l'implémentation de l'objet. En d'autres termes, l'implémentation réelle serait placée dans le
.c
fichier, tandis que l'interface serait placée dans l'en-tête.h
fichier d' . Par exemple, en utilisant l'exemple de singe ci-dessus:L'interface ressemblerait à:
Vous pouvez voir dans le
.h
fichier d' interface que vous ne définissez que des prototypes. Vous pouvez ensuite compiler le «.c
fichier» de la partie d'implémentation dans une bibliothèque statique ou dynamique. Cela crée une encapsulation et vous pouvez également modifier l'implémentation à volonté. L'utilisateur de votre objet n'a besoin de rien savoir de sa mise en œuvre. Cela met également l'accent sur la conception globale de l'objet.C'est ma conviction personnelle que oop est un moyen de conceptualiser votre structure de code et votre réutilisabilité et n'a vraiment rien à voir avec les autres choses qui sont ajoutées au C ++ comme la surcharge ou les modèles. Oui, ce sont de très belles fonctionnalités utiles mais elles ne sont pas représentatives de ce qu'est réellement la programmation orientée objet.
la source
typedef struct Monkey {} Monkey;
Quel est l'intérêt de la saisir après sa création?struct _monkey
est simplement un prototype. La définition de type réelle est définie dans le fichier d'implémentation (le fichier .c). Cela crée l'effet d'encapsulation et permet au développeur d'API de redéfinir la structure du singe à l'avenir sans modifier l'API. Les utilisateurs de l'API doivent uniquement se préoccuper des méthodes réelles. Le concepteur d'API s'occupe de l'implémentation, y compris de la disposition de l'objet / de la structure. Ainsi, les détails de l'objet / structure sont cachés à l'utilisateur (un type opaque).int getCount(ObjectType obj)
etc. si vous choisissez de définir la structure dans le fichier d'implémentation.Ma recommandation: restez simple. L'un des plus gros problèmes que j'ai est de maintenir des logiciels plus anciens (parfois 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 un polymorphisme en C, mais cela peut être difficile à lire.
Je préfère les objets simples qui encapsulent des fonctionnalités bien définies. Un bon exemple de ceci est GLIB2 , par exemple une table de hachage:
Les clés sont:
la source
Si j'allais écrire la POO en CI, j'irais probablement avec un pseudo- Pimpl conception . Au lieu de passer des pointeurs vers des structures, vous finissez par passer des pointeurs vers des pointeurs vers des structures. Cela rend le contenu opaque et facilite le polymorphisme et l'héritage.
Le vrai problème avec la POO en C est ce qui se passe lorsque les variables quittent la portée. Il n'y a pas de destructeurs générés par le compilateur et cela peut causer des problèmes. Les macros peuvent éventuellement aider, mais ce sera toujours moche à regarder.
la source
if
instructions et en les libérant à la fin. Par exempleif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Une autre façon de programmer dans un style orienté objet avec C est d'utiliser un générateur de code qui transforme un langage spécifique à un domaine en C. Comme c'est le cas avec TypeScript et JavaScript pour amener la POO à js.
la source
Production:
Voici un spectacle de ce qu'est la programmation OO avec C.
C'est du vrai C pur, pas de macros de préprocesseur. Nous avons l'héritage, le polymorphisme et l'encapsulation des données (y compris les données privées aux classes ou aux objets). Il n'y a aucune chance pour l'équivalent de qualificatif protégé, c'est-à-dire que les données privées sont également privées tout au long de la chaîne d'héritage. Mais ce n'est pas un inconvénient car je ne pense pas que ce soit nécessaire.
CPolygon
n'est pas instancié car nous ne l'utilisons que pour manipuler des objets en aval de la chaîne d'héritage qui ont des aspects communs mais une implémentation différente de ceux-ci (polymorphisme).la source
@Adam Rosenfield a une très bonne explication sur la façon de réaliser la POO avec C
D'ailleurs, je vous recommande de lire
1) pjsip
Une très bonne bibliothèque C pour la VoIP. Vous pouvez apprendre comment il réalise la POO via des structures et des tables de pointeurs de fonction
2) Exécution iOS
Découvrez comment iOS Runtime alimente l'Objectif C.Il réalise la POO via isa pointer, méta-classe
la source
Pour moi, l'orientation objet en C devrait avoir ces caractéristiques:
Encapsulation et masquage des données (peut être réalisé à l'aide de structs / pointeurs opaques)
Héritage et prise en charge du polymorphisme (l'héritage unique peut être obtenu à l'aide de structs - assurez-vous que la base abstraite n'est pas instanciable)
Fonctionnalité du constructeur et du destructeur (pas facile à réaliser)
Vérification de type (au moins pour les types définis par l'utilisateur, car C n'en applique aucun)
Comptage de références (ou quelque chose pour implémenter RAII )
Prise en charge limitée de la gestion des exceptions (setjmp et longjmp)
En plus de ce qui précède, il doit s'appuyer sur les spécifications ANSI / ISO et ne doit pas s'appuyer sur des fonctionnalités spécifiques au compilateur.
la source
Regardez http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Si rien d'autre, lire la documentation est une expérience enrichissante.
la source
Je suis un peu en retard à la fête ici, mais j'aime éviter les deux macro extrêmes - trop ou trop de code obscurcit, mais quelques macros évidentes peuvent rendre le code POO plus facile à développer et à lire:
Je pense que cela a un bon équilibre, et les erreurs qu'il génère (au moins avec les options par défaut de gcc 6.3) pour certaines des erreurs les plus probables sont utiles au lieu de dérouter. Le but est d'améliorer la productivité du programmeur non?
la source
Si vous avez besoin d'écrire un petit code, essayez ceci: https://github.com/fulminati/class-framework
la source
Je travaille également là-dessus sur la base d'une solution macro. Donc c'est pour les plus courageux, je suppose ;-) Mais c'est déjà assez sympa, et je travaille déjà sur quelques projets en plus. Cela fonctionne de sorte que vous définissiez d'abord un fichier d'en-tête séparé pour chaque classe. Comme ça:
Pour implémenter la classe, vous créez un fichier d'en-tête pour elle et un fichier C dans lequel vous implémentez les méthodes:
Dans l'en-tête que vous avez créé pour la classe, vous incluez les autres en-têtes dont vous avez besoin et définissez les types, etc. liés à la classe. Dans l'en-tête de classe et dans le fichier C, vous incluez le fichier de spécification de classe (voir le premier exemple de code) et une macro X. Ces X-macros ( 1 , 2 , 3 etc.) étendront le code aux structures de classe réelles et autres déclarations.
Pour hériter d'une classe,
#define SUPER supername
et ajoutersupername__define \
comme première ligne dans la définition de classe. Les deux doivent être là. Il existe également un support JSON, des signaux, des classes abstraites, etc.Pour créer un objet, utilisez simplement
W_NEW(classname, .x=1, .y=2,...)
. L'initialisation est basée sur l'initialisation struct introduite dans C11. Cela fonctionne bien et tout ce qui n'est pas répertorié est mis à zéro.Pour appeler une méthode, utilisez
W_CALL(o,method)(1,2,3)
. Cela ressemble à un appel de fonction d'ordre supérieur, mais ce n'est qu'une macro. Il se développe pour devenir((o)->klass->method(o,1,2,3))
un très bon hack.Voir la documentation et le code lui-même .
Puisque le framework a besoin d'un code standard, j'ai écrit un script Perl (wobject) qui fait le travail. Si vous utilisez cela, vous pouvez simplement écrire
et il créera le fichier de spécification de classe, l'en-tête de classe et un fichier C, qui inclut
Point_impl.c
où vous implémentez la classe. Cela économise beaucoup de travail, si vous avez beaucoup de classes simples mais que tout est toujours en C. wobject est un scanner très simple basé sur des expressions régulières qui est facile à adapter à des besoins spécifiques ou à réécrire à partir de zéro.la source
C'est exactement ce que fait le projet open source Dynace. C'est à https://github.com/blakemcbride/Dynace
la source
Vous pouvez essayer COOP , un framework convivial pour les programmeurs pour la POO en C, qui comprend les classes, les exceptions, le polymorphisme et la gestion de la mémoire (important pour le code incorporé). C'est une syntaxe relativement légère, voir le tutoriel dans le Wiki .
la source