Quels sont les moyens d'écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.
Voir aussi cette question Stack Overflow orienté objet en C .
Quels sont les moyens d'écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.
Voir aussi cette question Stack Overflow orienté objet en C .
Réponses:
Oui. En fait, Axel Schreiner fournit gratuitement son livre "Programmation orientée objet en ANSI-C" qui couvre le sujet de manière assez approfondie.
Puisque vous parlez de polymorphisme, alors oui, vous pouvez, nous faisions ce genre de choses des années avant la création de C ++.
Fondamentalement, vous utilisez un struct
pour contenir à la fois les données et une liste de pointeurs de fonction pour pointer vers les fonctions pertinentes pour ces données.
Ainsi, dans une classe de communication, vous auriez un appel ouvert, lu, écrit et fermé qui serait maintenu comme quatre pointeurs de fonction dans la structure, à côté des données pour un objet, quelque chose comme:
typedef struct {
int (*open)(void *self, char *fspec);
int (*close)(void *self);
int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
// And data goes here.
} tCommClass;
tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;
tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;
Bien sûr, ces segments de code ci-dessus seraient en fait dans un "constructeur" tel que rs232Init()
.
Lorsque vous «héritez» de cette classe, vous changez simplement les pointeurs pour pointer vers vos propres fonctions. Tous ceux qui ont appelé ces fonctions le feraient via les pointeurs de fonction, vous donnant votre polymorphisme:
int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");
Un peu comme une table manuelle.
Vous pouvez même avoir des classes virtuelles en définissant les pointeurs sur NULL - le comportement serait légèrement différent de C ++ (un vidage de mémoire au moment de l'exécution plutôt qu'une erreur au moment de la compilation).
Voici un exemple de code qui le démontre. Tout d'abord la structure de classe de niveau supérieur:
#include <stdio.h>
// The top-level class.
typedef struct sCommClass {
int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;
Ensuite, nous avons les fonctions de la «sous-classe» TCP:
// Function for the TCP 'class'.
static int tcpOpen (tCommClass *tcp, char *fspec) {
printf ("Opening TCP: %s\n", fspec);
return 0;
}
static int tcpInit (tCommClass *tcp) {
tcp->open = &tcpOpen;
return 0;
}
Et le HTTP aussi:
// Function for the HTTP 'class'.
static int httpOpen (tCommClass *http, char *fspec) {
printf ("Opening HTTP: %s\n", fspec);
return 0;
}
static int httpInit (tCommClass *http) {
http->open = &httpOpen;
return 0;
}
Et enfin un programme de test pour le montrer en action:
// Test program.
int main (void) {
int status;
tCommClass commTcp, commHttp;
// Same 'base' class but initialised to different sub-classes.
tcpInit (&commTcp);
httpInit (&commHttp);
// Called in exactly the same manner.
status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
status = (commHttp.open)(&commHttp, "http://www.microsoft.com");
return 0;
}
Cela produit la sortie:
Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com
vous pouvez donc voir que les différentes fonctions sont appelées, selon la sous-classe.
tCommClass
serait renommé tCommVT
, et une tCommClass
structure n'aurait que des champs de données et un seul tCommVT vt
champ pointant vers la "seule et unique" table virtuelle. Porter tous les pointeurs autour de chaque instance ajoute une surcharge inutile et ressemble plus à la façon dont vous feriez des choses en JavaScript qu'à C ++, à mon humble avis.
Les espaces de noms se font souvent en faisant:
stack_push(thing *)
au lieu de
stack::push(thing *)
Pour transformer une structure C en quelque chose comme une classe C ++ , vous pouvez activer:
class stack {
public:
stack();
void push(thing *);
thing * pop();
static int this_is_here_as_an_example_only;
private:
...
};
Dans
struct stack {
struct stack_type * my_type;
// Put the stuff that you put after private: here
};
struct stack_type {
void (* construct)(struct stack * this); // This takes uninitialized memory
struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
int this_is_here_as_an_example_only;
}Stack = {
.construct = stack_construct,
.operator_new = stack_operator_new,
.push = stack_push,
.pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else
Et fait:
struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
// Do something about it
} else {
// You can use the stack
stack_push(st, thing0); // This is a non-virtual call
Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
st->my_type.push(st, thing2); // This is a virtual call
}
Je n'ai pas fait le destructeur ni supprimé, mais il suit le même schéma.
this_is_here_as_an_example_only est comme une variable de classe statique - partagée entre toutes les instances d'un type. Toutes les méthodes sont vraiment statiques, sauf que certaines prennent ceci *
st->my_type->push(st, thing2);
au lieu dest->my_type.push(st, thing2);
struct stack_type my_type;
au lieu destruct stack_type * my_type;
Class
structure générique ? Cela rendrait l'OO C plus dynamique que C ++. Et ça? Au fait, +1.
Je crois qu'en plus d'être utile en soi, la mise en œuvre de la POO en C est un excellent moyen d' apprendre la POO et de comprendre son fonctionnement interne. L'expérience de nombreux programmeurs a montré que pour utiliser une technique de manière efficace et en toute confiance, un programmeur doit comprendre comment les concepts sous-jacents sont finalement mis en œuvre. L'émulation des classes, de l'héritage et du polymorphisme en C enseigne exactement cela.
Pour répondre à la question d'origine, voici quelques ressources qui enseignent comment faire la POO en C:
Le billet de blog d'EmbeddedGurus.com "Programmation basée sur les objets en C" montre comment implémenter les classes et l'héritage unique dans le C portable: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /
La note d'application "" C + "- Programmation orientée objet en C" montre comment implémenter les classes, l'héritage unique et la liaison tardive (polymorphisme) en C à l'aide de macros de préprocesseur: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , l'exemple de code est disponible sur http://www.state-machine.com/resources/cplus_3.0.zip
Je l'ai vu faire. Je ne le recommanderais pas. C ++ a commencé à l'origine de cette façon en tant que préprocesseur qui a produit du code C comme étape intermédiaire.
Essentiellement, vous finissez par créer une table de répartition pour toutes vos méthodes où vous stockez vos références de fonction. Dériver une classe impliquerait de copier cette table de répartition et de remplacer les entrées que vous vouliez remplacer, vos nouvelles "méthodes" devant appeler la méthode d'origine si elle voulait invoquer la méthode de base. Finalement, vous finissez par réécrire C ++.
glib
écrit en C de manière objective?
Bien sûr que c'est possible. C'est ce que fait GObject , le framework sur lequel GTK + et GNOME sont basés.
La sous-bibliothèque C stdio FILE est un excellent exemple de la façon de créer l'abstraction, l'encapsulation et la modularité en C. non altéré.
L'héritage et le polymorphisme - les autres aspects souvent considérés comme essentiels à la POO - n'apportent pas nécessairement les gains de productivité qu'ils promettent et des arguments raisonnables ont été avancés selon lesquels ils peuvent réellement entraver le développement et la réflexion sur le domaine problématique.
Exemple trivial avec un animal et un chien: vous reflétez le mécanisme vtable de C ++ (en grande partie de toute façon). Vous séparez également l'allocation et l'instanciation (Animal_Alloc, Animal_New) afin que nous n'appelions pas malloc () plusieurs fois. Nous devons également passer explicitement le this
pointeur.
Si vous deviez faire des fonctions non virtuelles, c'est trival. Vous ne les ajoutez tout simplement pas aux fonctions vtable et statiques ne nécessitent pas de this
pointeur. L'héritage multiple nécessite généralement plusieurs vtables pour résoudre les ambiguïtés.
De plus, vous devriez pouvoir utiliser setjmp / longjmp pour gérer les exceptions.
struct Animal_Vtable{
typedef void (*Walk_Fun)(struct Animal *a_This);
typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);
Walk_Fun Walk;
Dtor_Fun Dtor;
};
struct Animal{
Animal_Vtable vtable;
char *Name;
};
struct Dog{
Animal_Vtable vtable;
char *Name; // Mirror member variables for easy access
char *Type;
};
void Animal_Walk(struct Animal *a_This){
printf("Animal (%s) walking\n", a_This->Name);
}
struct Animal* Animal_Dtor(struct Animal *a_This){
printf("animal::dtor\n");
return a_This;
}
Animal *Animal_Alloc(){
return (Animal*)malloc(sizeof(Animal));
}
Animal *Animal_New(Animal *a_Animal){
a_Animal->vtable.Walk = Animal_Walk;
a_Animal->vtable.Dtor = Animal_Dtor;
a_Animal->Name = "Anonymous";
return a_Animal;
}
void Animal_Free(Animal *a_This){
a_This->vtable.Dtor(a_This);
free(a_This);
}
void Dog_Walk(struct Dog *a_This){
printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}
Dog* Dog_Dtor(struct Dog *a_This){
// Explicit call to parent destructor
Animal_Dtor((Animal*)a_This);
printf("dog::dtor\n");
return a_This;
}
Dog *Dog_Alloc(){
return (Dog*)malloc(sizeof(Dog));
}
Dog *Dog_New(Dog *a_Dog){
// Explict call to parent constructor
Animal_New((Animal*)a_Dog);
a_Dog->Type = "Dog type";
a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;
return a_Dog;
}
int main(int argc, char **argv){
/*
Base class:
Animal *a_Animal = Animal_New(Animal_Alloc());
*/
Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());
a_Animal->vtable.Walk(a_Animal);
Animal_Free(a_Animal);
}
PS. Ceci est testé sur un compilateur C ++, mais il devrait être facile de le faire fonctionner sur un compilateur C.
typedef
l'intérieur d'un struct
n'est pas possible en C.
Découvrez GObject . Il est censé être OO en C et une implémentation de ce que vous recherchez. Si vous voulez vraiment OO, optez pour C ++ ou un autre langage OOP. Il peut être parfois difficile de travailler avec GObject si vous avez l'habitude de gérer les langages OO, mais comme tout, vous vous habituerez aux conventions et au flux.
Cela a été intéressant à lire. J'ai moi-même réfléchi à la même question, et les avantages d'y penser sont les suivants:
Essayer d'imaginer comment implémenter des concepts OOP dans un langage non-OOP m'aide à comprendre les points forts du langage OOp (dans mon cas, C ++). Cela m'aide à mieux juger de l'utilisation du C ou du C ++ pour un type d'application donné - où les avantages de l'un l'emportent sur l'autre.
Dans ma navigation sur le Web pour obtenir des informations et des opinions à ce sujet, j'ai trouvé un auteur qui écrivait du code pour un processeur intégré et n'avait qu'un compilateur C disponible: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Création-Fondation-Classes-Part-1
Dans son cas, l'analyse et l'adaptation des concepts de POO en simple C était une poursuite valable. Il semble qu'il était prêt à sacrifier certains concepts de POO en raison du dépassement des performances résultant de la tentative de les implémenter en C.
La leçon que j'ai tirée est, oui, cela peut être fait dans une certaine mesure, et oui, il y a de bonnes raisons de l'essayer.
En fin de compte, la machine tourne des bits de pointeur de pile, faisant sauter le compteur de programme et calculant les opérations d'accès à la mémoire. Du point de vue de l'efficacité, moins ces calculs sont effectués par votre programme, mieux c'est ... mais parfois nous devons payer cette taxe simplement pour pouvoir organiser notre programme de manière à le rendre moins vulnérable aux erreurs humaines. Le compilateur de langage OOP s'efforce d'optimiser les deux aspects. Le programmeur doit être beaucoup plus prudent lors de la mise en œuvre de ces concepts dans un langage comme C.
Il peut être utile de consulter la documentation d'Apple pour son ensemble d'API Core Foundation. Il s'agit d'une pure API C, mais de nombreux types sont pontés vers des équivalents d'objet Objective-C.
Vous pouvez également trouver utile de regarder la conception d'Objective-C lui-même. C'est un peu différent de C ++ en ce que le système objet est défini en termes de fonctions C, par exemple objc_msg_send
pour appeler une méthode sur un objet. Le compilateur traduit la syntaxe entre crochets en ces appels de fonction, vous n'avez donc pas besoin de la connaître, mais compte tenu de votre question, vous trouverez peut-être utile d'apprendre comment cela fonctionne sous le capot.
Il existe plusieurs techniques qui peuvent être utilisées. Le plus important est davantage de savoir comment diviser le projet. Nous utilisons une interface dans notre projet qui est déclarée dans un fichier .h et l'implémentation de l'objet dans un fichier .c. La partie importante est que tous les modules qui incluent le fichier .h ne voient qu'un objet en tant que a void *
, et le fichier .c est le seul module qui connaît les éléments internes de la structure.
Quelque chose comme ça pour une classe que nous nommons FOO comme exemple:
Dans le fichier .h
#ifndef FOO_H_
#define FOO_H_
...
typedef struct FOO_type FOO_type; /* That's all the rest of the program knows about FOO */
/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif
Le fichier d'implémentation C sera quelque chose comme ça.
#include <stdlib.h>
...
#include "FOO.h"
struct FOO_type {
whatever...
};
FOO_type *FOO_new(void)
{
FOO_type *this = calloc(1, sizeof (FOO_type));
...
FOO_dosomething(this, );
return this;
}
Je donne donc le pointeur explicitement à un objet pour chaque fonction de ce module. Un compilateur C ++ le fait implicitement, et en C nous l'écrivons explicitement.
J'utilise vraiment this
dans mes programmes, pour m'assurer que mon programme ne compile pas en C ++, et il a la belle propriété d'être dans une autre couleur dans mon éditeur de coloration syntaxique.
Les champs du FOO_struct peuvent être modifiés dans un module et un autre module n'a même pas besoin d'être recompilé pour être toujours utilisable.
Avec ce style, je gère déjà une grande partie des avantages de la POO (encapsulation de données). En utilisant des pointeurs de fonction, il est même facile d'implémenter quelque chose comme l'héritage, mais honnêtement, ce n'est vraiment que rarement utile.
typedef struct FOO_type FOO_type
place d'un typedef à annuler dans l'en-tête, vous obtenez l'avantage supplémentaire de la vérification de type, tout en n'exposant pas votre structure.
Vous pouvez le simuler en utilisant des pointeurs de fonction, et en fait, je pense qu'il est théoriquement possible de compiler des programmes C ++ en C.
Cependant, il est rarement logique d'imposer un paradigme à une langue plutôt que de choisir une langue qui utilise un paradigme.
C orienté objet, peut être fait, j'ai vu ce type de code en production en Corée, et c'était le monstre le plus horrible que j'avais vu depuis des années (c'était comme l'année dernière (2007) que j'ai vu le code). Alors oui, cela peut être fait, et oui, les gens l'ont déjà fait, et le font encore de nos jours. Mais je recommanderais C ++ ou Objective-C, les deux sont des langages nés de C, dans le but de fournir une orientation d'objet avec différents paradigmes.
Si vous êtes convaincu qu'une approche POO est supérieure pour le problème que vous essayez de résoudre, pourquoi tenteriez-vous de le résoudre avec un langage non POO? Il semble que vous n'utilisiez pas le bon outil pour le travail. Utilisez C ++ ou un autre langage variant C orienté objet.
Si vous demandez parce que vous commencez à coder sur un grand projet déjà existant écrit en C, alors vous ne devriez pas essayer de forcer vos propres paradigmes de POO (ou ceux de quelqu'un d'autre) dans l'infrastructure du projet. Suivez les directives qui sont déjà présentes dans le projet. En général API, propres et les bibliothèques et les modules isolés vont un long chemin vers avoir un OOP- propre ish design.
Si, après tout cela, vous êtes vraiment prêt à faire de la POO C, lisez ceci (PDF).
Oui, vous pouvez. Les gens écrivaient du C orienté objet avant que C ++ ou Objective-C n'entrent en scène. C ++ et Objective-C étaient, en partie, des tentatives de prendre certains des concepts OO utilisés en C et de les formaliser dans le cadre du langage.
Voici un programme très simple qui montre comment vous pouvez créer quelque chose qui ressemble à / est un appel de méthode (il existe de meilleures façons de le faire. C'est juste la preuve que le langage prend en charge les concepts):
#include<stdio.h>
struct foobarbaz{
int one;
int two;
int three;
int (*exampleMethod)(int, int);
};
int addTwoNumbers(int a, int b){
return a+b;
}
int main()
{
// Define the function pointer
int (*pointerToFunction)(int, int) = addTwoNumbers;
// Let's make sure we can call the pointer
int test = (*pointerToFunction)(12,12);
printf ("test: %u \n", test);
// Now, define an instance of our struct
// and add some default values.
struct foobarbaz fbb;
fbb.one = 1;
fbb.two = 2;
fbb.three = 3;
// Now add a "method"
fbb.exampleMethod = addTwoNumbers;
// Try calling the method
int test2 = fbb.exampleMethod(13,36);
printf ("test2: %u \n", test2);
printf("\nDone\n");
return 0;
}
Un petit code OOC à ajouter:
#include <stdio.h>
struct Node {
int somevar;
};
void print() {
printf("Hello from an object-oriented C method!");
};
struct Tree {
struct Node * NIL;
void (*FPprint)(void);
struct Node *root;
struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};
int main()
{
struct Tree TreeB;
TreeB = TreeA;
TreeB.FPprint();
return 0;
}
Je creuse ça depuis un an:
Comme le système GObject est difficile à utiliser avec du C pur, j'ai essayé d'écrire de belles macros pour faciliter le style OO avec C.
#include "OOStd.h"
CLASS(Animal) {
char *name;
STATIC(Animal);
vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
THIS->name = name;
return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)
CLASS_EX(Cat,Animal) {
STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
printf("Meow!My name is %s!\n", THIS->name);
}
static int Cat_loadSt(StAnimal *THIS, void *PARAM){
THIS->talk = (void *)Meow;
return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)
CLASS_EX(Dog,Animal){
STATIC_EX(Dog, Animal);
};
static void Woof(Animal *THIS){
printf("Woof!My name is %s!\n", THIS->name);
}
static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
THIS->talk = (void *)Woof;
return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)
int main(){
Animal *animals[4000];
StAnimal *f;
int i = 0;
for (i=0; i<4000; i++)
{
if(i%2==0)
animals[i] = NEW(Dog,"Jack");
else
animals[i] = NEW(Cat,"Lily");
};
f = ST(animals[0]);
for(i=0; i<4000; ++i) {
f->talk(animals[i]);
}
for (i=0; i<4000; ++i) {
DELETE0(animals[i]);
}
return 0;
}
Voici mon site de projet (je n'ai pas assez de temps pour écrire en. Doc, cependant la doc en chinois est bien meilleure).
Il est un exemple d'héritage en C en 1996 parler de Jim Larson donné à la Section 312 Programmation Séminaire midi ici: haut et bas niveau C .
Quels articles ou livres sont bons pour utiliser les concepts de POO en C?
Les interfaces et implémentations C de Dave Hanson sont excellentes pour l'encapsulation et la dénomination et très bonnes pour l'utilisation des pointeurs de fonction. Dave n'essaie pas de simuler l'héritage.
Une chose que vous voudrez peut-être faire est d'examiner la mise en œuvre de la boîte à outils Xt pour X Window . Bien sûr, cela prend du temps dans la dent, mais la plupart des structures utilisées ont été conçues pour fonctionner de manière OO dans le C. traditionnel.Généralement, cela signifie ajouter une couche supplémentaire d'indirection ici et là et concevoir des structures à superposer.
Vous pouvez vraiment faire beaucoup de choses sur la façon dont OO situé en C de cette façon, même si cela en a parfois l'air, les concepts OO ne sont pas complètement nés de l'esprit de #include<favorite_OO_Guru.h>
. Ils constituaient vraiment bon nombre des meilleures pratiques établies de l'époque. Les langages et systèmes OO ne distillent et n'amplifient que des parties de la programmation du jour.
La réponse à la question est «Oui, vous le pouvez».
Le kit C orienté objet (OOC) est destiné à ceux qui souhaitent programmer de manière orientée objet, mais il colle également au bon vieux C. OOC implémente les classes, l'héritage simple et multiple, la gestion des exceptions.
Caractéristiques
• Utilise uniquement des macros et des fonctions C, aucune extension de langue requise! (ANSI-C)
• Code source facile à lire pour votre application. On a pris soin de rendre les choses aussi simples que possible.
• Héritage unique des classes
• Héritage multiple par interfaces et mixins (depuis la version 1.3)
• Implémentation d'exceptions (en C pur!)
• Fonctions virtuelles pour les classes
• Outil externe pour une implémentation de classe facile
Pour plus de détails, visitez http://ooc-coding.sourceforge.net/ .
Il semble que les gens essaient d'émuler le style C ++ en utilisant C. Mon point de vue est que faire de la programmation orientée objet C fait vraiment de la programmation orientée struct. Cependant, vous pouvez réaliser des choses comme la liaison tardive, l'encapsulation et l'héritage. Pour l'héritage, vous définissez explicitement un pointeur vers les structures de base dans votre sous-structure et c'est évidemment une forme d'héritage multiple. Vous devrez également déterminer si votre
//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);
//private_class.c
struct inherited_class_1;
struct inherited_class_2;
struct private_class {
int a;
int b;
struct inherited_class_1 *p1;
struct inherited_class_2 *p2;
};
struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();
struct private_class * new_private_class() {
struct private_class *p;
p = (struct private_class*) malloc(sizeof(struct private_class));
p->a = 0;
p->b = 0;
p->p1 = new_inherited_class_1();
p->p2 = new_inherited_class_2();
return p;
}
int ret_a_value(struct private_class *p, int a, int b) {
return p->a + p->b + a + b;
}
void delete_private_class(struct private_class *p) {
//release any resources
//call delete methods for inherited classes
free(p);
}
//main.c
struct private_class *p;
p = new_private_class();
late_bind_function = &implementation_function;
delete_private_class(p);
compiler avec c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj
.
Le conseil est donc de s'en tenir à un style C pur et de ne pas essayer de forcer dans un style C ++. De plus, cette méthode se prête à une manière très propre de créer une API.
Voir http://slkpg.byethost7.com/instance.html pour encore une autre torsion sur la POO en C. Il met l'accent sur les données d'instance pour la réentrance en utilisant uniquement le C. natif. L'héritage multiple se fait manuellement à l'aide de wrappers de fonction. La sécurité du type est maintenue. Voici un petit échantillon:
typedef struct _peeker
{
log_t *log;
symbols_t *sym;
scanner_t scan; // inherited instance
peek_t pk;
int trace;
void (*push) ( SELF *d, symbol_t *symbol );
short (*peek) ( SELF *d, int level );
short (*get) ( SELF *d );
int (*get_line_number) ( SELF *d );
} peeker_t, SlkToken;
#define push(self,a) (*self).push(self, a)
#define peek(self,a) (*self).peek(self, a)
#define get(self) (*self).get(self)
#define get_line_number(self) (*self).get_line_number(self)
INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
return d->scan.line_number;
}
PUBLIC
void
InitializePeeker ( peeker_t *peeker,
int trace,
symbols_t *symbols,
log_t *log,
list_t *list )
{
InitializeScanner ( &peeker->scan, trace, symbols, log, list );
peeker->log = log;
peeker->sym = symbols;
peeker->pk.current = peeker->pk.buffer;
peeker->pk.count = 0;
peeker->trace = trace;
peeker->get_line_number = get_line_number;
peeker->push = push;
peeker->get = get;
peeker->peek = peek;
}
Je suis un peu en retard à la fête, mais je veux partager mon expérience sur le sujet: je travaille avec des trucs embarqués de nos jours, et le seul compilateur (fiable) que j'ai est C, donc je veux appliquer une approche orientée objet approche dans mes projets embarqués écrits en C.
La plupart des solutions que j'ai vues jusqu'à présent utilisent fortement les typecasts, nous perdons donc la sécurité des types: le compilateur ne vous aidera pas si vous faites une erreur. C'est totalement inacceptable.
Exigences que j'ai:
J'ai expliqué mon approche en détail dans cet article: Programmation orientée objet en C ; de plus, il existe un utilitaire pour la génération automatique de code passe-partout pour les classes de base et dérivées.
J'ai construit une petite bibliothèque où j'ai essayé ça et pour moi ça marche vraiment bien. J'ai donc pensé partager l'expérience.
https://github.com/thomasfuhringer/oxygen
L'héritage unique peut être implémenté assez facilement en utilisant une structure et en l'étendant pour toutes les autres classes enfants. Un simple cast vers la structure parent permet d'utiliser des méthodes parents sur tous les descendants. Tant que vous savez qu'une variable pointe vers une structure contenant ce type d'objet, vous pouvez toujours transtyper vers la classe racine et effectuer une introspection.
Comme cela a été mentionné, les méthodes virtuelles sont quelque peu plus délicates. Mais ils sont réalisables. Pour garder les choses simples, j'utilise simplement un tableau de fonctions dans la structure de description de classe que chaque classe enfant copie et repeuple les emplacements individuels si nécessaire.
L'héritage multiple serait plutôt compliqué à implémenter et aurait un impact significatif sur les performances. Je le laisse donc. Je considère qu'il est souhaitable et utile dans de nombreux cas de modéliser proprement les circonstances de la vie réelle, mais dans 90% des cas, l'héritage unique couvre probablement les besoins. Et l'héritage unique est simple et ne coûte rien.
De plus, je ne me soucie pas de la sécurité des types. Je pense que vous ne devriez pas dépendre du compilateur pour vous éviter des erreurs de programmation. Et cela ne vous protège de toute façon que d'une petite partie des erreurs.
En règle générale, dans un environnement orienté objet, vous souhaitez également implémenter le comptage des références pour automatiser autant que possible la gestion de la mémoire. J'ai donc également mis un décompte de références dans la classe racine «Object» et quelques fonctionnalités pour encapsuler l'allocation et la désallocation de la mémoire du tas.
Tout cela est très simple et léger et me donne l'essentiel d'OO sans me forcer à faire face au monstre qu'est le C ++. Et je conserve la flexibilité de rester en C land, ce qui facilite entre autres l'intégration de bibliothèques tierces.
Je propose d'utiliser Objective-C, qui est un surensemble de C.
Si Objective-C a 30 ans, il permet d'écrire du code élégant.
Oui, mais je n'ai jamais vu personne tenter d'implémenter un quelconque polymorphisme avec C.