web-dev-qa-db-fra.com

Comment pourrait-on écrire du code orienté objet en C?

Comment peut-on écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.


Voir aussi Question de débordement de pile Orientation d'objet en C.

472
Dinah

Oui. En fait, Axel Schreiner fournit gratuitement son livre "Programmation orientée objet en ANSI-C" qui couvre le sujet de manière assez complète.

347
mepcotterell

Puisque vous parlez de polymorphisme, alors oui, nous pouvions le faire, des années avant l’apparition du C++.

Fondamentalement, vous utilisez un struct pour stocker les données et une liste de pointeurs de fonctions pour pointer vers les fonctions appropriées pour ces données.

Ainsi, dans une classe de communication, vous auriez un appel d'ouverture, de lecture, d'écriture et de fermeture qui serait maintenu sous la forme de quatre pointeurs de fonction dans la structure, aux côtés des données d'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 entendu, ces segments de code ci-dessus seraient en réalité dans un "constructeur" tel que rs232Init().

Lorsque vous "héritez" de cette classe, il vous suffit de changer les pointeurs pour pointer vers vos propres fonctions. Tous ceux qui ont appelé ces fonctions le feraient à travers les pointeurs de fonction, vous donnant ainsi votre polymorphisme:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Un peu comme un vtable manuel.

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++ (vidage de la mémoire au moment de l'exécution plutôt qu'une erreur lors 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 pour la TCP 'sous-classe':

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

Ceci 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, en fonction de la sous-classe.

327
paxdiablo

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 transformer:

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 ou supprimer, mais il suit le même modèle.

this_is_here_as_an_example_only est comme une variable de classe statique - partagée par toutes les instances d'un type. Toutes les méthodes sont vraiment statiques, sauf que certaines prennent cette *

83
nategoose

Je crois qu’en plus d’être utile en soi, la mise en oeuvre de OOP en C est un excellent moyen d’apprendre à apprendre OOP et 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. Émuler des classes, l'héritage et le polymorphisme en C enseigne cela.

Pour répondre à la question initiale, voici quelques ressources qui vous apprennent à faire OOP en C:

L'article de blog d'EmbeddedGurus.com "Programmation basée sur les objets en C" montre comment implémenter des classes et l'héritage simple dans un C: 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 des classes, l'héritage simple 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 à partir de http://www.state-machine.com/resources/cplus_3.0.Zip

53
Miro Samek

Je l'ai vu faire. Je ne le recommanderais pas. C++ a initialement commencé de cette façon en tant que pré-processeur qui produisait du code C en tant qu'étape intermédiaire.

En fin de compte, vous créez une table de répartition pour toutes vos méthodes dans lesquelles vous stockez vos références de fonction. Pour dériver une classe, vous devez copier cette table de répartition et remplacer les entrées que vous souhaitez remplacer, vos nouvelles "méthodes" devant appeler la méthode d'origine si elle souhaite appeler la méthode de base. Finalement, vous finissez par réécrire C++.

32
tvanfosson

Bien sûr que c'est possible. C’est sur quoi GObject , le cadre sur lequel GTK + et GNOME est basé,.

26

La sous-bibliothèque C stdio FILE est un excellent exemple de la création d’abstraction, d’encapsulation et de modularité dans un C non altéré.

Héritage et polymorphisme - les autres aspects souvent considérés comme essentiels pour OOP - ne procurent pas nécessairement les gains de productivité promis et raisonnablesdes arguments ont été établis indiquant qu'ils peuvent réellement entraver le développement et la réflexion sur le domaine du problème .

17
msw

Exemple trivial avec un animal et un chien: vous reproduisez le mécanisme vtable de C++ (en tout cas, en grande partie). 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 explicitement faire passer le pointeur this.

Si vous deviez faire des fonctions non virtuelles, c'est une trivalence. Vous ne les ajoutez pas à la table vtable et les fonctions statiques ne nécessitent pas de pointeur this. L'héritage multiple nécessite généralement plusieurs vtables pour résoudre les ambiguïtés.

En outre, 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.

15
Jasper Bekkers

Départ GObject . C'est censé être OO en C et une implémentation de ce que vous cherchez. Si vous voulez vraiment OO cependant, optez pour C++ ou un autre langage OOP. Il est parfois très difficile de travailler avec GObject si vous êtes habitué à utiliser les langages OO, mais comme d'habitude, vous vous habituerez aux conventions et à la fluidité.

13
NG.

Cela a été intéressant à lire. J'ai moi-même réfléchi à la même question et voici les avantages de cette réflexion:

  • Essayer d'imaginer comment implémenter OOP concepts dans un langage non-OOP m'aide à comprendre les points forts du langage OOp (dans mon cas, C++). Cela me permet d’avoir un meilleur jugement sur l’utilisation éventuelle de C ou C++ pour un type d’application donné - où les avantages de l’une sont supérieurs à ceux de l’autre.

  • En parcourant le Web pour trouver des informations et des opinions à ce sujet, j'ai trouvé un auteur qui écrivait du code pour un processeur intégré et ne disposait que d'un compilateur C: http://www.eetimes.com/discussion/other/4024626/Classes de base de création-C-Création-Foundation-Part-1

Dans son cas, analyser et adapter les concepts OOP en clair était un exercice valable. Il semble qu'il était prêt à sacrifier certains OOP concepts en raison de la perte de temps de performance résultant de la tentative de les mettre en œuvre en C.

La leçon que j'ai tirée est que, oui, cela peut être fait dans une certaine mesure, et oui, il y a de bonnes raisons de tenter l'expérience.

En fin de compte, la machine tourne les bits du pointeur de la pile, ce qui force le compteur de programme à se déplacer et calcule les opérations d'accès à la mémoire. Du point de vue de l'efficacité, moins votre calcul est effectué par votre programme, mieux c'est ... mais nous devons parfois 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 le C.

12
RJB

Plusieurs techniques peuvent être utilisées. Le plus important est plus 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 un objet que comme un void *, et que 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, par 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 explicitement le pointeur à 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 qu'il a la propriété d'être dans une autre couleur dans mon éditeur de coloration syntaxique.

Les champs de la structure FOO_struct peuvent être modifiés dans un module et un autre module n'a même pas besoin d'être recompilé pour rester utilisable.

Avec ce style, je gère déjà une grande partie des avantages de OOP (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.

10
Patrick Schlüter

Vous trouverez peut-être utile de consulter la documentation d'Apple pour son ensemble d'API Core Foundation. C'est une API C pure, mais beaucoup de types sont pontés vers des équivalents d'objet Objective-C.

Vous pouvez également trouver utile de regarder la conception d’Objective-C elle-même. C'est un peu différent de C++ en ce que le système d'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 jugerez peut-être utile de savoir comment cela fonctionne sous le capot.

10
benzado

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) où j'ai vu le code). Donc, oui, cela peut être fait, et oui, les gens l'ont déjà fait et le font encore, même 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.

7
Robert Gould

Si vous êtes convaincu qu'une approche OOP est supérieure au problème que vous tentez de résoudre, pourquoi voudriez-vous essayer de le résoudre avec un langage autre que OOP? Il semble que vous utilisiez le mauvais outil pour le travail. Utilisez C++ ou un autre langage C variante orienté objet.

Si vous posez la question parce que vous commencez à coder un grand projet déjà écrit en C, vous ne devriez pas essayer de forcer vos propres paradigmes OOP dans l'infrastructure du projet. Suivez les instructions déjà présentes dans le projet. En général, les API propres, les bibliothèques isolées et les modules contribueront grandement à une conception POO propre ish .

Si, après tout cela, vous êtes vraiment prêt à faire OOP C, lisez this (PDF).

7
RarrRarrRarr

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.

7
Uri

Oui, vous pouvez. Les gens écrivaient du C orienté objet avant que C++ ou Objective-C ne soit entré en scène. C++ et Objective-C essayaient, en partie, de reprendre certains des OO concepts utilisés en C et de les formaliser en tant que parties du langage.

Voici un programme très simple qui montre comment créer quelque chose qui ressemble à/est un appel de méthode (il existe de meilleures façons de le faire. Cela prouve simplement que le langage supporte 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;
}
6
Alan Storm

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

Bien sûr, ce ne sera tout simplement pas aussi joli que d’utiliser un langage avec support intégré. J'ai même écrit "assembleur orienté objet".

6
Darron

Je creuse cela depuis un an:

Comme le système GObject est difficile à utiliser avec du C pur, j'ai essayé d'écrire des macros Nice pour alléger 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 le site de mon projet (je n'ai pas assez de temps pour écrire en doc, mais la documentation en chinois est bien meilleure).

OOC-GCC

5
dameng

Il y a un exemple d'héritage utilisant le C dans l'exposé de Jim Larson de 1996 donné lors du séminaire de la section 312 sur la programmation ici: niveau élevé et bas C .

4
Judge Maygarden

On dirait que les gens essaient d'imiter le style C++ en utilisant C. Mon point de vue est que faire de la programmation orientée objet C est vraiment une programmation orientée structure. Toutefois, vous pouvez réaliser des tâches telles que la liaison tardive, l’encapsulation et l’héritage. Pour l'héritage, vous définissez explicitement un pointeur sur les structures de base de votre sous-structure et il s'agit évidemment d'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 un style C++. Cette méthode se prête également à un moyen très propre de créer une API.

4
user2074102

La réponse à la question est "oui, vous pouvez".

Le kit orienté objet (OOC) est destiné à ceux qui souhaitent programmer de manière orientée objet, tout en conservant le bon vieux C. OOC implémente les classes, l'héritage unique et multiple, la gestion des exceptions.

Caractéristiques

• Utilise uniquement des macros et des fonctions C, aucune extension de langue n’est requise! (ANSI-C)

• Code source facile à lire pour votre application. Nous avons pris soin de rendre les choses aussi simples que possible.

• Héritage unique de classes

• Héritage multiple par interfaces et mixins (depuis la version 1.3)

• Implémentation des exceptions (en C pur!)

• Fonctions virtuelles pour les cours

• Outil externe pour une mise en œuvre facile de la classe

Pour plus de détails, visitez http://ooc-coding.sourceforge.net/ .

4
Sachin Mhetre

Quels articles ou livres convient-il d'utiliser OOP concepts en C?

Dave Hanson's Interfaces C et implémentations est excellent en ce qui concerne l'encapsulation et la dénomination et très bon pour l'utilisation des pointeurs de fonction. Dave n'essaie pas de simuler l'héritage.

4
Norman Ramsey

Une des choses que vous voudrez peut-être faire est de regarder l’implémentation du toolkit Xt pour X Window . Certes, cela prend du temps, mais bon nombre des structures utilisées ont été conçues pour fonctionner de manière OO au sein des centres traditionnels C. Cela implique généralement l’ajout d’une couche supplémentaire d’indirection ici et là et la conception de structures l'un sur l'autre.

Vous pouvez vraiment faire beaucoup de choses dans le sens où OO situé en C de cette façon, même si on en a l'impression parfois, les concepts OO ne sont pas entièrement issus de l'esprit de #include<favorite_OO_Guru.h>. Ils constituaient vraiment bon nombre des meilleures pratiques établies de l’époque. OO langues et systèmes uniquement les parties distillées et amplifiées de l’atmosphère de programmation du jour.

4
Ukko

La POO n'est qu'un paradigme qui place les données plus importantes que le code dans les programmes. OOP n'est pas une langue. Donc, comme C simple est un langage simple, OOP en C simple est aussi simple.

4
anonyme

J'ai construit une petite bibliothèque où j'ai essayé et qui fonctionne vraiment bien pour moi. Alors j'ai pensé partager l'expérience.

https://github.com/thomasfuhringer/oxygen

L'héritage simple peut être implémenté assez facilement en utilisant une structure et en l'étendant pour toutes les autres classes enfants. Un simple transtypage sur la structure parente permet d'utiliser des méthodes parentes sur tous les descendants. Tant que vous savez qu'une variable pointe sur une structure contenant ce type d'objet, vous pouvez toujours effectuer un transtypage vers la classe racine et effectuer une introspection.

Comme cela a été mentionné, les méthodes virtuelles sont un peu plus délicates. Mais ils sont faisables. Pour simplifier les choses, j'utilise simplement un tableau de fonctions dans la structure de description de classe, que chaque classe enfant copie et repopule si nécessaire.

L'héritage multiple serait plutôt compliqué à mettre en œuvre et aurait un impact significatif sur les performances. Alors je le laisse. J'estime qu'il est souhaitable et utile dans de nombreux cas de modéliser proprement les circonstances de la vie réelle, mais dans probablement 90% des cas, l'héritage unique couvre les besoins. Et l'héritage simple est simple et ne coûte rien.

En outre, je ne me soucie pas de la sécurité de type. Je pense que vous ne devriez pas dépendre du compilateur pour vous empêcher de commettre des erreurs de programmation. Et cela ne vous protège de toute façon que d'une petite partie des erreurs.

Généralement, dans un environnement orienté objet, vous souhaitez également implémenter le comptage de références pour automatiser autant que possible la gestion de la mémoire. Donc, j'ai également mis un compte de référence dans la classe racine "Object" et des fonctionnalités pour encapsuler l'allocation et la désallocation de la mémoire heap.

Tout cela est très simple et maigre et me donne l'essentiel de OO sans me forcer à traiter avec le monstre qu'est C++. Et je garde la possibilité de rester dans le pays C, ce qui facilite notamment l'intégration de bibliothèques tierces.

2
Thomas F.

Je suis un peu en retard à la fête, mais je souhaite partager mon expérience sur le sujet: je travaille avec des éléments incorporés ces derniers temps et le seul compilateur (fiable) que j'ai est C, de sorte que je veuille appliquer une approche orientée objet. approche dans mes projets intégrés écrits en C.

La plupart des solutions que j'ai vues jusqu'à présent utilisent beaucoup de dactylographes, nous perdons donc la sécurité des types: le compilateur ne vous aidera pas si vous faites une erreur. C'est complètement inacceptable.

Exigences que j'ai:

  • Évitez autant que possible les dactylographes afin de ne pas perdre la sécurité des types;
  • Polymorphisme: nous devrions pouvoir utiliser des méthodes virtuelles et l'utilisateur de la classe ne devrait pas savoir si une méthode particulière est virtuelle ou non;
  • Héritage multiple: je ne l'utilise pas souvent, mais parfois je veux vraiment qu'une classe implémente plusieurs interfaces (ou étende plusieurs super-classes).

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.

2
Dmitry Frank

Voir http://slkpg.byethost7.com/instance.html pour une autre tournure sur OOP 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 est fait manuellement en utilisant des wrappers de fonctions. Le type de sécurité est maintenu. 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;
}
2
slkpg

Je propose d'utiliser Objective-C, qui est un sur-ensemble de C.

Alors que Objective-C a 30 ans, il permet d’écrire du code élégant.

http://en.wikipedia.org/wiki/Objective-C

1
SteAp

Oui, mais je n'ai jamais vu personne tenter d'implémenter un polymorphisme avec C.

0
Paul Morel

Oui c'est possible.

C'est du C pur, pas de prétraitement de macros. Il a l'héritage, le polymorphisme, l'encapsulation de données (y compris les données privées). Il n'a pas de qualificateur protégé équivalent, ce qui signifie que les données privées sont également privées dans la chaîne d'héritage.

#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

/*output:
6.56
13.12
*/
0
rogergc

Je pense que la première chose à dire est que la mise en œuvre de pointeurs de fonction par C (à mon humble avis) est VRAIMENT difficile à utiliser. Je sauterais à travers tout un tas de cerceaux pour éviter les indicateurs de fonction ...

cela dit, je pense que ce que les autres ont dit est plutôt bon. vous avez des structures, vous avez des modules, au lieu de foo->method(a,b,c), vous vous retrouvez avec method(foo,a,b,c) Si vous avez plus d'un type avec une méthode "method", vous pouvez alors le préfixer avec le type, donc FOO_method(foo,a,b,c), comme d'autres l'ont déjà dit ... avec une bonne utilisation des fichiers .h, vous pouvez obtenir des fichiers privés et publics, etc.

Maintenant, il y a quelques choses que cette technique ne vous donnera PAS. Cela ne vous donnera pas de champs de données privés. cela, je pense, est lié à la volonté et à une bonne hygiène de codage ... De plus, il n’ya pas de moyen facile de faire un héritage avec cela.

C’est au moins la partie la plus facile… le reste, je pense, est du genre 90/10. 10% du bénéfice nécessitera 90% du travail ...

0
Brian Postow