Pouvez-vous écrire du code orienté objet en C?


Answers

Puisque vous parlez de polymorphisme alors oui, vous pouvez, nous faisions ce genre de choses des années avant que le C ++ ne vienne.

Fondamentalement, vous utilisez une 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, en lecture, en écriture et en fermeture 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, les segments de code ci-dessus se trouveraient dans un "constructeur" tel que rs232Init() .

Lorsque vous 'héritez' de cette classe, vous changez juste les pointeurs pour pointer vers vos propres fonctions. Tous ceux qui appelaient ces fonctions le faisaient via les pointeurs de fonction, ce qui vous donnait 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 ++ (un vidage du noyau 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. D'abord la structure de classe de premier niveau:

#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 '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

Ainsi, vous pouvez voir que les différentes fonctions sont appelées, en fonction de la sous-classe.

Question

Pouvez-vous écrire du code orienté objet en C? Surtout en ce qui concerne le polymorphisme.

Voir aussi question Object-orientation dans C.




Voir http://slkpg.byethost7.com/instance.html pour encore une autre torsion sur OOP en C. Il met l'accent sur les données d'instance pour réentrer en utilisant juste natif C. L'héritage multiple est fait manuellement en utilisant des enveloppes 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;
}



Quels articles ou livres sont bons à utiliser les concepts de POO en C?

C Interfaces et Implémentations de Dave Hanson est excellent sur l'encapsulation et la dénomination et très bon sur l'utilisation des pointeurs de fonction. Dave n'essaie pas de simuler l'héritage.




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 efficacement et en toute confiance, un programmeur doit comprendre comment les concepts sous-jacents sont finalement mis en œuvre. Emuler des classes, l'héritage et le polymorphisme en C enseigne juste cela.

Pour répondre à la question originale, voici quelques ressources qui enseignent comment faire de la POO en C:

L'article de blog EmbeddedGurus.com "Programmation par objets en C" montre comment implémenter des classes et un héritage unique dans un C portable: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c/

Note d'application "" C + "- Programmation orientée objet en C" montre comment implémenter des classes, un héritage unique, et une liaison tardive (polymorphisme) en C en utilisant des 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




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

Le kit C (OOC) orienté objet est destiné à ceux qui veulent programmer de manière orientée objet, mais aussi sur le bon vieux C. OOC implémente les classes, l'héritage simple et multiple, la gestion des exceptions.

Caractéristiques

• Utilise uniquement les macros C et les fonctions, aucune extension de langue requise! (ANSI-C)

• Code source facile à lire pour votre application. Des précautions ont été prises pour 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 des exceptions (en C pur)

• Fonctions virtuelles pour les cours

• Outil externe pour une implémentation facile en classe

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




I built a little library where I tried that and to me it works real nicely. So I thought I share the experience.

https://github.com/thomasfuhringer/oxygen

Single inheritance can be implemented quite easily using a struct and extending it for every other child class. A simple cast to the parent structure makes it possible to use parent methods on all the descendants. As long as you know that a variable points to a struct holding this kind of an object you can always cast to the root class and do introspection.

As has been mentioned, virtual methods are somewhat trickier. But they are doable. To keep things simple I just use an array of functions in the class description structure which every child class copies and repopulates individual slots where required.

Multiple inheritance would be rather complicated to implement and comes with a significant performance impact. So I leave it. I do consider it desirable and useful in quite a few cases to cleanly model real life circumstances, but in probably 90% of cases single inheritance covers the needs. And single inheritance is simple and costs nothing.

Also I do not care about type safety. I think you should not depend on the compiler to prevent you from programming mistakes. And it shields you only from a rather small part of errors anyway.

Typically, in an object oriented environment you also want to implement reference counting to automate memory management to the extent possible. So I also put a reference count into the “Object” root class and some functionality to encapsulate allocation and deallocation of heap memory.

It is all very simple and lean and gives me the essentials of OO without forcing me to deal with the monster that is C++. And I retain the flexibility of staying in C land, which among other things makes it easier to integrate third party libraries.




Une chose que vous pourriez vouloir faire est de regarder dans l'implémentation de la boîte à outils Xt pour X Window . Bien sûr, il devient long dans la dent, mais la plupart des structures utilisées ont été conçues pour fonctionner de façon OO dans la forme C. Généralement cela signifie ajouter une couche supplémentaire d'indirection ici et là et concevoir des structures superposées.

Vous pouvez vraiment faire beaucoup de OO situé en C de cette façon, même si cela semble parfois le cas, les concepts OO ne sont pas entièrement formés à partir de l'esprit de #include<favorite_OO_Guru.h> . Ils ont vraiment constitué beaucoup de la meilleure pratique établie de l'époque. Les langages OO et les systèmes ne distillaient et amplifiaient que des parties du zeitgeist de programmation du jour.




Yes, but I have never seen anyone attempt to implement any sort of polymorphism with C.




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 de ne pas appeler malloc () plusieurs fois. Nous devons également passer explicitement this pointeur autour.

Si vous deviez faire des fonctions non virtuelles, c'est trivial. Vous ne les ajoutez pas à la vtable et les fonctions statiques ne requièrent pas this pointeur. 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.




Bien sûr que c'est possible. C'est ce que fait GObject , le framework sur lequel repose GTK+ et GNOME .




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




Il existe plusieurs techniques qui peuvent être utilisées. Le plus important est 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 voient seulement un objet comme un void * , et le fichier .c est le seul module qui connaît les 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;
}

Donc, je donne explicitement le pointeur à un objet à chaque fonction de ce module. Un compilateur C ++ le fait implicitement, et en C nous l'écrivons explicitement.

Je l'utilise vraiment 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 de FOO_struct peuvent être modifiés dans un module et un autre module n'a même pas besoin d'être recompilé pour être encore utilisable.

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




Vous pouvez le truquer 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 de forcer un paradigme sur une langue plutôt que de choisir un langage qui utilise un paradigme.




Si vous êtes convaincu qu'une approche POO est supérieure au problème que vous tentez de résoudre, pourquoi essaieriez-vous de le résoudre avec un langage non-POO? Il semble que vous utilisez le mauvais outil pour le travail. Utilisez C ++ ou un autre langage de variantes 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 OOP (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, les API propres et les bibliothèques et modules isolés vont très loin dans le sens d'une conception OOP- ish propre.

Si, après tout cela, vous êtes vraiment en train de faire de l'OOP C, lisez ceci (PDF).




Découvrez GObject . C'est censé être OO en C et une implémentation de ce que vous cherchez. Si vous voulez vraiment OO, allez avec C ++ ou un autre langage OOP. GObject peut être très difficile à utiliser parfois si vous avez l'habitude des langages OO, mais comme tout, vous vous habituerez aux conventions et aux flux.






Links