Que serait un ensemble de bidouilles astucieuses de préprocesseurs (compatibles ANSI C89/ISO C90) qui permettraient une sorte de vilaine orientation (mais utilisable) en C?
Je connais quelques langages orientés objet différents. Ne répondez donc pas avec des réponses telles que "Learn C++!". J'ai lu " Programmation orientée objet avec ANSI C " (attention: format PDF ) et plusieurs autres solutions intéressantes, mais Je suis surtout intéressé par le vôtre :-)!
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, y compris classes ouvertes, métaclasses, métaclasses de propriétés, génériques, méthodes multiples, délégation, propriété, exceptions, contrats et fermetures. Il existe un brouillon (PDF) qui le décrit.
Exception en C est une implémentation en C89 de TRY-CATCH-FINALEMENT trouvée dans d'autres langues OO). Elle est fournie avec une suite de tests et quelques exemples.
Les deux par Laurent Deniau, qui travaille beaucoup sur OOP en C .
Je déconseille l'utilisation du préprocesseur (ab) pour essayer de rendre la syntaxe C plus proche de celle d'un autre langage plus orienté objet. Au niveau le plus élémentaire, il vous suffit d'utiliser des structures simples en tant qu'objets et de les faire suivre par des pointeurs:
struct monkey
{
float age;
bool is_male;
int happiness;
};
void monkey_dance(struct monkey *monkey)
{
/* do a little dance */
}
Pour obtenir des choses comme l'héritage et le polymorphisme, vous devez travailler un peu plus fort. Vous pouvez effectuer un héritage manuel en faisant en sorte que le premier membre d'une structure soit une instance de la superclasse. Vous pouvez ensuite créer librement des pointeurs vers les classes de base et les classes dérivées:
struct base
{
/* base class members */
};
struct derived
{
struct base super;
/* derived class members */
};
struct derived d;
struct base *base_ptr = (struct base *)&d; // upcast
struct derived *derived_ptr = (struct derived *)base_ptr; // downcast
Pour obtenir du polymorphisme (c'est-à-dire des fonctions virtuelles), vous utilisez des pointeurs de fonction et éventuellement des tables de pointeurs de fonction, également appelées tables virtuelles ou vtables:
struct base;
struct base_vtable
{
void (*dance)(struct base *);
void (*jump)(struct base *, int how_high);
};
struct base
{
struct base_vtable *vtable;
/* base members */
};
void base_dance(struct base *b)
{
b->vtable->dance(b);
}
void base_jump(struct base *b, int how_high)
{
b->vtable->jump(b, how_high);
}
struct derived1
{
struct base super;
/* derived1 members */
};
void derived1_dance(struct derived1 *d)
{
/* implementation of derived1's dance function */
}
void derived1_jump(struct derived1 *d, int how_high)
{
/* implementation of derived 1's jump function */
}
/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
&derived1_dance, /* you might get a warning here about incompatible pointer types */
&derived1_jump /* you can ignore it, or perform a cast to get rid of it */
};
void derived1_init(struct derived1 *d)
{
d->super.vtable = &derived1_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
struct derived2
{
struct base super;
/* derived2 members */
};
void derived2_dance(struct derived2 *d)
{
/* implementation of derived2's dance function */
}
void derived2_jump(struct derived2 *d, int how_high)
{
/* implementation of derived2's jump function */
}
struct base_vtable derived2_vtable =
{
&derived2_dance,
&derived2_jump
};
void derived2_init(struct derived2 *d)
{
d->super.vtable = &derived2_vtable;
/* init base members d->super.foo */
/* init derived1 members d->foo */
}
int main(void)
{
/* OK! We're done with our declarations, now we can finally do some
polymorphism in C */
struct derived1 d1;
derived1_init(&d1);
struct derived2 d2;
derived2_init(&d2);
struct base *b1_ptr = (struct base *)&d1;
struct base *b2_ptr = (struct base *)&d2;
base_dance(b1_ptr); /* calls derived1_dance */
base_dance(b2_ptr); /* calls derived2_dance */
base_jump(b1_ptr, 42); /* calls derived1_jump */
base_jump(b2_ptr, 42); /* calls derived2_jump */
return 0;
}
Et c'est comme ça que vous faites le polymorphisme en C. Ce n'est pas joli, mais ça fait le travail. Il existe certains problèmes épineux liés aux transformations de pointeurs entre les classes de base et dérivées, qui sont sécurisées 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 séparer les classes de base autres que la première, vous devez ajuster manuellement les pointeurs en fonction des décalages appropriés, ce qui est très délicat et sujet aux erreurs.
Une autre chose (délicate) que vous pouvez faire est de changer le type dynamique d'un objet à l'exécution! Vous venez de réaffecter un nouveau pointeur vtable. Vous pouvez même modifier de manière sélective certaines fonctions virtuelles tout en en conservant d'autres, créant ainsi de nouveaux types hybrides. Veillez simplement à créer une nouvelle table virtuelle au lieu de modifier la table virtuelle globale, sinon vous affecteriez accidentellement tous les objets d'un type donné.
J'ai déjà travaillé avec une bibliothèque C qui a été mise en œuvre d'une manière qui m'a semblé assez élégante. Ils avaient écrit, en C, un moyen de définir des objets, puis d’en hériter pour qu’ils soient aussi extensibles qu’un objet C++. L'idée de base était la suivante:
Il est difficile de décrire l'héritage, mais au fond c'était:
struct vehicle {
int power;
int weight;
}
Puis dans un autre fichier:
struct van {
struct vehicle base;
int cubic_size;
}
Vous pourriez alors avoir une camionnette créée en mémoire et utilisée par un code qui ne connaissait que les véhicules:
struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );
Cela a fonctionné à merveille et les fichiers .h ont défini exactement ce que vous devriez être capable de faire avec chaque objet.
Le bureau GNOME pour Linux est écrit en C orienté objet et possède un modèle objet appelé " GObject " qui prend en charge les propriétés, l’héritage, le polymorphisme, ainsi que quelques autres avantages tels que les références, la gestion des événements ( appelés "signaux"), typage d’exécution, données privées, etc.
Cela inclut des astuces de préprocesseur pour faire des choses comme le transtypage dans la hiérarchie des classes, etc. Voici un exemple de classe que j'ai écrit pour GNOME (des choses comme gchar sont typedefs):
Dans la structure GObject, il existe un entier GType utilisé comme nombre magique pour le système de frappe dynamique de GLib (vous pouvez convertir la structure entière en "GType" pour trouver son type).
Si vous considérez les méthodes appelées sur des objets comme des méthodes statiques qui transmettent un 'this
' implicite à la fonction, cela facilite la réflexion OO en C plus facile.
Par exemple:
String s = "hi";
System.out.println(s.length());
devient:
string s = "hi";
printf(length(s)); // pass in s, as an implicit this
Ou quelque chose comme ça.
J'avais l'habitude de faire ce genre de chose en C, avant de savoir ce que c'était OOP.
Voici un exemple qui implémente un tampon de données qui grandit à la demande, avec une taille minimale, un incrément et une taille maximale. Cette implémentation particulière reposait sur des éléments, c’est-à-dire qu’elle était conçue pour permettre une collection semblable à une liste de tout 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 "membre" nécessite l'utilisation d'un pointeur spécifiquement typé.
J'ai mis en œuvre 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 proposé par Kieveli pourrait être un bon chemin.
dtb.c:
#include <limits.h>
#include <string.h>
#include <stdlib.h>
static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
DTABUF *dbp;
if(!minsiz) { return NULL; }
if(!incsiz) { incsiz=minsiz; }
if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz; }
if(minsiz+incsiz>maxsiz) { incsiz=maxsiz-minsiz; }
if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
memset(dbp,0,sizeof(*dbp));
dbp->min=minsiz;
dbp->inc=incsiz;
dbp->max=maxsiz;
dbp->siz=minsiz;
dbp->cur=0;
if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
return dbp;
}
DTABUF *dtb_dlt(DTABUF *dbp) {
if(dbp) {
free(dbp->dta);
free(dbp);
}
return NULL;
}
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
if(!dbp) { errno=EINVAL; return -1; }
if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
if((dbp->cur + dtalen) > dbp->siz) {
void *newdta;
vint newsiz;
if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
else { newsiz=dbp->cur+dtalen; }
if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
dbp->dta=newdta; dbp->siz=newsiz;
}
if(dtalen) {
if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
else { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen); }
dbp->cur+=dtalen;
}
return 0;
}
static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
byte *sp,*dp;
for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
}
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
byte textÝ501¨;
va_list ap;
vint len;
va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
else { va_start(ap,format); vsprintf(text,format,ap); va_end(ap); }
return dtb_adddta(dbp,xlt256,text,len);
}
vint dtb_rmvdta(DTABUF *dbp,vint len) {
if(!dbp) { errno=EINVAL; return -1; }
if(len > dbp->cur) { len=dbp->cur; }
dbp->cur-=len;
return 0;
}
vint dtb_reset(DTABUF *dbp) {
if(!dbp) { errno=EINVAL; return -1; }
dbp->cur=0;
if(dbp->siz > dbp->min) {
byte *newdta;
if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
free(dbp->dta); dbp->dta=null; dbp->siz=0;
return -1;
}
dbp->dta=newdta; dbp->siz=dbp->min;
}
return 0;
}
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
return ((byte*)dbp->dta+(elmidx*elmlen));
}
dtb.h
typedef _Packed struct {
vint min; /* initial size */
vint inc; /* increment size */
vint max; /* maximum size */
vint siz; /* current size */
vint cur; /* current data length */
void *dta; /* data pointer */
} DTABUF;
#define dtb_dtaptr(mDBP) (mDBP->dta)
#define dtb_dtalen(mDBP) (mDBP->cur)
DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF *dtb_dlt(DTABUF *dbp);
vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint dtb_rmvdta(DTABUF *dbp,vint len);
vint dtb_reset(DTABUF *dbp);
void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);
PS: vint était simplement un typedef d’int - je l’ai utilisé pour me rappeler que sa longueur était variable d’une plate-forme à l’autre (pour le portage).
ffmpeg (une boîte à outils pour le traitement vidéo) est écrit en C (et en langage d'assemblage), mais en utilisant un style orienté objet. C'est plein de structures avec des pointeurs de fonction. Il existe un ensemble de fonctions d'usine qui initialisent les structures avec les pointeurs "méthodes" appropriés.
Si vous pensez vraiment bien, même la bibliothèque C standard utilise OOP - considérons FILE *
Comme exemple: fopen()
initialise un objet FILE *
, et vous l'utilisez, utilisez les méthodes de membre fscanf()
, fprintf()
, fread()
, fwrite()
et autres, puis finalisez-le avec fclose()
.
Vous pouvez également choisir la méthode pseudo-Objective-C, qui n’est pas difficile non plus:
typedef void *Class;
typedef struct __class_Foo
{
Class isa;
int ivar;
} Foo;
typedef struct __meta_Foo
{
Foo *(*alloc)(void);
Foo *(*init)(Foo *self);
int (*ivar)(Foo *self);
void (*setIvar)(Foo *self);
} meta_Foo;
meta_Foo *class_Foo;
void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
class_Foo = malloc(sizeof(meta_Foo));
if (class_Foo)
{
class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
}
}
Foo *__imp_Foo_alloc(void)
{
Foo *foo = malloc(sizeof(Foo));
if (foo)
{
memset(foo, 0, sizeof(Foo));
foo->isa = class_Foo;
}
return foo;
}
Foo *__imp_Foo_init(Foo *self)
{
if (self)
{
self->ivar = 42;
}
return self;
}
// ...
Utiliser:
int main(void)
{
Foo *foo = (class_Foo->init)((class_Foo->alloc)());
printf("%d\n", (foo->isa->ivar)(foo)); // 42
foo->isa->setIvar(foo, 60);
printf("%d\n", (foo->isa->ivar)(foo)); // 60
free(foo);
}
C'est ce qui peut résulter de certains codes Objective-C comme celui-ci, si un très ancien traducteur Objective-C-to-C est utilisé:
@interface Foo : NSObject
{
int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end
@implementation Foo
- (id)init
{
if (self = [super init])
{
ivar = 42;
}
return self;
}
@end
int main(void)
{
Foo *foo = [[Foo alloc] init];
printf("%d\n", [foo ivar]);
[foo setIvar:60];
printf("%d\n", [foo ivar]);
[foo release];
}
Je pense que ce que Adam Rosenfield a écrit est la bonne façon de faire OOP en C. J'aimerais ajouter que ce qu'il montre est la mise en œuvre de l'objet. En d'autres termes, la mise en œuvre réelle serait mets dans le .c
fichier, l'interface étant placée dans l'en-tête .h
fichier. Par exemple, en utilisant l'exemple de singe ci-dessus:
L'interface ressemblerait à ceci:
//monkey.h
struct _monkey;
typedef struct _monkey monkey;
//memory management
monkey * monkey_new();
int monkey_delete(monkey *thisobj);
//methods
void monkey_dance(monkey *thisobj);
Vous pouvez voir dans l'interface .h
fichier que vous définissez uniquement des prototypes. Vous pouvez ensuite compiler la partie implémentation ".c
fichier "dans une bibliothèque statique ou dynamique. Cela crée une encapsulation et vous pouvez également modifier l’implémentation à votre guise. L’utilisateur de votre objet n’a presque rien à savoir à propos de son implémentation. objet.
Je crois personnellement que oop est un moyen de conceptualiser la structure de votre code et sa réutilisabilité et qu’il n’a vraiment rien à voir avec les autres éléments ajoutés à c ++, tels que la surcharge ou les modèles. Oui, ce sont des fonctionnalités très utiles, mais elles ne sont pas représentatives de ce que la programmation orientée objet est réellement.
Une autre façon de programmer dans un style orienté objet avec C consiste à 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, apportez OOP en js.
Si je devais écrire OOP dans CI irait probablement avec un pseudo - Pimpl design. Au lieu de passer des pointeurs à des structures, vous finissez par les faire passer à des pointeurs vers Cela rend le contenu opaque et facilite le polymorphisme et l'héritage.
Le vrai problème avec OOP en C est ce qui se passe lorsque les variables quittent la portée. Il n’existe pas de destructeurs générés par le compilateur et qui peuvent poser problème. Macros peut éventuellement aider, mais il sera toujours moche de regarder.
Ma recommandation: restez simple. L’un des plus gros problèmes que j’ai à faire est de maintenir des logiciels plus anciens (parfois âgés de plus de 10 ans). Si le code n'est pas simple, cela peut être difficile. Oui, on peut écrire très utile OOP 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 est GLIB2 , par exemple une table de hachage:
GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...
g_hash_table_remove(my_hash, some_key);
Les clés sont:
Le projet open-source Dynace fait exactement cela. C'est à https://github.com/blakemcbride/Dynace
J'y travaille également sur une solution macro. Donc, c'est seulement pour les plus courageux, je suppose ;-) Mais c'est déjà assez gentil, 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:
#define CLASS Point
#define BUILD_JSON
#define Point__define \
METHOD(Point,public,int,move_up,(int steps)) \
METHOD(Point,public,void,draw) \
\
VAR(read,int,x,JSON(json_int)) \
VAR(read,int,y,JSON(json_int)) \
Pour implémenter la classe, vous créez un fichier d’en-tête et un fichier C dans lesquels vous implémentez les méthodes:
METHOD(Point,public,void,draw)
{
printf("point at %d,%d\n", self->x, self->y);
}
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 la classe et dans le fichier C, vous incluez le fichier de spécification de la classe (voir le premier exemple de code) et une macro X. Ces macros X ( 1 , 2 , etc.) étendront le code aux structures de classe réelles et à d'autres déclarations.
Pour hériter d'une classe, #define SUPER supername
Et ajoutez supername__define \
Comme première ligne de la définition de la 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 de la structure 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 c’est juste une macro. Il se développe en ((o)->klass->method(o,1,2,3))
Qui est un hack vraiment sympa.
Voir Documentation et le code lui-même .
Puisque le framework a besoin de code standard, j'ai écrit un script Perl (wobject) qui fait le travail. Si vous utilisez cela, vous pouvez simplement écrire
class Point
public int move_up(int steps)
public void draw()
read int x
read int y
et il créera le fichier de spécification de classe, un en-tête de classe et un fichier C, qui comprend Point_impl.c
dans lequel vous implémentez la classe. Cela économise beaucoup de travail, si vous avez beaucoup de classes simples mais que tout est en C. wobject est un scanner très simple basé sur les expressions régulières qui est facile à adapter à des besoins spécifiques ou à être réécrit. à partir de rien.
#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);
}
Sortie:
6.56
13.12
Voici un spectacle de ce qui est OO programmation avec C.
C’est réel, du C pur, sans macros de préprocesseur. Nous avons l'héritage, le polymorphisme et l'encapsulation de données (y compris les données privées de classes ou d'objets). Il n’ya aucune chance que l’équivalent de qualificatif protégé soit protégé, c’est-à-dire que les données privées sont également privées dans la chaîne de la propriété innée. Mais ce n’est pas un inconvénient car je ne pense pas que ce soit nécessaire.
CPolygon
n'est pas instancié parce que nous ne l'utilisons que pour manipuler des objets de la chaîne de l'héritage qui ont des aspects communs mais une implémentation différente (le polymorphisme).
Pour moi, l'orientation des objets en C devrait avoir les caractéristiques suivantes:
Encapsulation et masquage des données (peut être réalisé à l'aide de structures/pointeurs opaques)
Héritage et prise en charge du polymorphisme (l'héritage simple peut être obtenu à l'aide de structures - assurez-vous que la base abstraite n'est pas instanciable)
Fonctionnalité constructeur et destructeur (pas facile à réaliser)
Vérification du 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 à implémenter RAII )
Prise en charge limitée du traitement des exceptions (setjmp et longjmp)
En plus de ce qui précède, il devrait s’appuyer sur les spécifications ANSI/ISO et non sur les fonctionnalités spécifiques du compilateur.
@Adam Rosenfield a une très bonne explication sur la façon d’atteindre OOP avec C
En outre, je vous recommande de lire
1) pjsip
Une très bonne bibliothèque C pour la VoIP. Vous pouvez apprendre comment cela se passe OOP si les tables de structure et de pointeur de fonction
2) iOS Runtime
Découvrez comment iOS Runtime permet à Objective C. Il atteint OOP par le biais d'un pointeur, méta-classe
Regardez http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Si rien d’autre, lire la documentation est une expérience enrichissante.
Je suis un peu en retard pour la fête ici, mais j'aime éviter les deux macro-extrêmes - trop ou trop de code obscurcissant, mais quelques macros évidentes peuvent faciliter la lecture du code OOP développer et lire:
/*
* OOP in C
*
* gcc -o oop oop.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
struct obj2d {
float x; // object center x
float y; // object center y
float (* area)(void *);
};
#define X(obj) (obj)->b1.x
#define Y(obj) (obj)->b1.y
#define AREA(obj) (obj)->b1.area(obj)
void *
_new_obj2d(int size, void * areafn)
{
struct obj2d * x = calloc(1, size);
x->area = areafn;
// obj2d constructor code ...
return x;
}
// --------------------------------------------------------
struct rectangle {
struct obj2d b1; // base class
float width;
float height;
float rotation;
};
#define WIDTH(obj) (obj)->width
#define HEIGHT(obj) (obj)->height
float rectangle_area(struct rectangle * self)
{
return self->width * self->height;
}
#define NEW_rectangle() _new_obj2d(sizeof(struct rectangle), rectangle_area)
// --------------------------------------------------------
struct triangle {
struct obj2d b1;
// deliberately unfinished to test error messages
};
#define NEW_triangle() _new_obj2d(sizeof(struct triangle), triangle_area)
// --------------------------------------------------------
struct circle {
struct obj2d b1;
float radius;
};
#define RADIUS(obj) (obj)->radius
float circle_area(struct circle * self)
{
return M_PI * self->radius * self->radius;
}
#define NEW_circle() _new_obj2d(sizeof(struct circle), circle_area)
// --------------------------------------------------------
#define NEW(objname) (struct objname *) NEW_##objname()
int
main(int ac, char * av[])
{
struct rectangle * obj1 = NEW(rectangle);
struct circle * obj2 = NEW(circle);
X(obj1) = 1;
Y(obj1) = 1;
// your decision as to which of these is clearer, but note above that
// macros also hide the fact that a member is in the base class
WIDTH(obj1) = 2;
obj1->height = 3;
printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));
X(obj2) = 10;
Y(obj2) = 10;
RADIUS(obj2) = 1.5;
printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));
// WIDTH(obj2) = 2; // error: struct circle has no member named width
// struct triangle * obj3 = NEW(triangle); // error: triangle_area undefined
}
Je pense que cela présente un bon équilibre et que les erreurs qu’elles génèrent (du moins avec les options par défaut de gcc 6.3) pour certaines des erreurs les plus probables sont utiles au lieu d’être déroutantes. Le but est d’améliorer la productivité des programmeurs non?