¿Puedes escribir código orientado a objetos en C?

¿Puedes escribir código orientado a objetos en C? Especialmente con respecto al polymorphism.


Consulte también Pregunta de desbordamiento de stack Orientación de objeto en C.

Sí. De hecho, Axel Schreiner ofrece su libro “Progtwigción orientada a objetos en ANSI-C” de forma gratuita que cubre el tema bastante a fondo.

Ya que estás hablando de polymorphism, entonces sí, puedes, estábamos haciendo ese tipo de cosas años antes de que surgiera C ++.

Básicamente se usa una struct para contener los datos y una lista de punteros de función para señalar las funciones relevantes para esos datos.

Entonces, en una clase de comunicaciones, tendría una llamada abierta, de lectura, de escritura y de cierre que se mantendría como cuatro punteros de función en la estructura, junto con los datos de un objeto, algo así como:

 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; 

Por supuesto, esos segmentos de código anteriores estarían realmente en un “constructor” como rs232Init() .

Cuando ‘heredas’ de esa clase, solo cambias los punteros para señalar tus propias funciones. Todos los que llamaron a esas funciones lo harían a través de los indicadores de función, dándote tu polymorphism:

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

Algo así como un vtable manual.

Incluso podría tener clases virtuales estableciendo los punteros en NULL -el comportamiento sería ligeramente diferente a C ++ (un volcado del núcleo en tiempo de ejecución en lugar de un error en tiempo de comstackción).

Aquí hay un fragmento de código de muestra que lo demuestra. Primero la estructura de clase de nivel superior:

 #include  // The top-level class. typedef struct sCommClass { int (*open)(struct sCommClass *self, char *fspec); } tCommClass; 

Entonces tenemos las funciones para la ‘subclase’ 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; } 

Y el HTTP también:

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

Y finalmente un progtwig de prueba para mostrarlo en acción:

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

Esto produce la salida:

 Opening TCP: bigiron.box.com:5000 Opening HTTP: http://www.microsoft.com 

para que pueda ver que se están llamando a las diferentes funciones, dependiendo de la subclase.

Los espacios de nombres a menudo se hacen haciendo:

 stack_push(thing *) 

en lugar de

 stack::push(thing *) 

Para convertir una estructura C en algo así como una clase C ++ , puede activar:

 class stack { public: stack(); void push(thing *); thing * pop(); static int this_is_here_as_an_example_only; private: ... }; 

Dentro

 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 

Y hacer:

 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 } 

No hice el destructor o eliminé, pero sigue el mismo patrón.

this_is_here_as_an_example_only es como una variable de clase estática, compartida entre todas las instancias de un tipo. Todos los métodos son realmente estáticos, excepto que algunos toman esto *

Creo que además de ser útil por derecho propio, implementar OOP en C es una excelente manera de aprender OOP y comprender su funcionamiento interno. La experiencia de muchos progtwigdores ha demostrado que para usar una técnica de manera eficiente y con confianza, un progtwigdor debe comprender cómo se implementan los conceptos subyacentes. Emular clases, herencia y polymorphism en C enseña esto.

Para responder la pregunta original, aquí hay un par de recursos que enseñan cómo hacer OOP en C:

La publicación de blog de EmbeddedGurus.com “Progtwigción basada en objetos en C” muestra cómo implementar clases y herencia individual en C portátil: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Nota de aplicación “” C + “- Progtwigción orientada a objetos en C” muestra cómo implementar clases, herencia única y enlace tardío (polymorphism) en C usando macros de preprocesador: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , el código de ejemplo está disponible en http://www.state-machine.com/resources/cplus_3.0.zip

Lo he visto hecho No lo recomendaría. C ++ originalmente comenzó de esta manera como un preprocesador que producía código C como un paso intermedio.

Esencialmente, lo que terminas haciendo es crear una tabla de envío para todos tus métodos donde almacenas tus referencias de funciones. Derivar una clase implicaría copiar esta tabla de envío y reemplazar las entradas que desea anular, con sus nuevos “métodos” que tienen que llamar al método original si desea invocar el método base. Eventualmente, terminas reescribiendo C ++.

Claro que es posible. Esto es lo que hace GObject , el marco en el que se basan todos los GTK + y GNOME .

La sub biblioteca C stdio FILE es un excelente ejemplo de cómo crear abstracción, encapsulado y modularidad en C. no adulterada.

La herencia y el polymorphism -los otros aspectos que a menudo se consideran esenciales para la OOP- no necesariamente proporcionan los aumentos de productividad que prometen y se han presentado argumentos razonables que en realidad pueden obstaculizar el desarrollo y pensar sobre el dominio del problema.

Ejemplo trivial con un Animal y un Perro: Usted refleja el mecanismo vtable de C ++ (en gran medida de todos modos). También separa la asignación y la creación de instancias (Animal_Alloc, Animal_New) por lo que no llamamos malloc () varias veces. También debemos pasar explícitamente this puntero alrededor.

Si tuviera que hacer funciones no virtuales, eso es trival. Simplemente no los agrega a las funciones vtable y estáticas no requieren this puntero. La herencia múltiple generalmente requiere varios vtables para resolver ambigüedades.

Además, debería poder usar setjmp / longjmp para manejar excepciones.

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

PD. Esto se prueba en un comstackdor de C ++, pero debería ser fácil hacerlo funcionar en un comstackdor de C.

Echa un vistazo a GObject . Está destinado a ser OO en C y una implementación de lo que estás buscando. Si realmente quieres OO, ve con C ++ u otro lenguaje OOP. GObject puede ser realmente difícil de trabajar a veces si estás acostumbrado a tratar con lenguajes OO, pero como todo, te acostumbrarás a las convenciones y al flujo.

Esto ha sido interesante de leer. He estado reflexionando sobre la misma pregunta yo mismo, y los beneficios de pensarlo son los siguientes:

  • Tratar de imaginar cómo implementar conceptos de OOP en un lenguaje que no sea de OOP me ayuda a comprender las fortalezas del lenguaje OOp (en mi caso, C ++). Esto me ayuda a tener un mejor juicio sobre si usar C o C ++ para un tipo dado de aplicación, donde los beneficios de uno superan al otro.

  • En mi navegación por la web en busca de información y opiniones sobre esto, encontré un autor que estaba escribiendo código para un procesador integrado y solo tenía un comstackdor de C disponible: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Creating-Foundation-Classes-Part-1

En su caso, el análisis y la adaptación de los conceptos de OOP en el plano C era una actividad válida. Parece que estaba dispuesto a sacrificar algunos conceptos de progtwigción orientada a objetos (OOP, por sus siglas en inglés) debido al impacto general en el rendimiento resultante de intentar implementarlos en C.

La lección que he tomado es, sí, se puede hacer hasta cierto punto, y sí, hay algunas buenas razones para intentarlo.

Al final, la máquina está moviendo los bits del puntero de la stack, haciendo que el contador del progtwig salte y calcule las operaciones de acceso a la memoria. Desde el punto de vista de la eficiencia, cuantos menos cálculos realice su progtwig, mejor … pero a veces tenemos que pagar este impuesto simplemente para que podamos organizar nuestro progtwig de una manera que lo haga menos susceptible a errores humanos. El comstackdor de lenguaje OOP se esfuerza por optimizar ambos aspectos. El progtwigdor tiene que ser mucho más cuidadoso al implementar estos conceptos en un lenguaje como C.

Puede que le resulte útil consultar la documentación de Apple para su conjunto de API de Core Foundation. Es una API C pura, pero muchos de los tipos están puenteados con equivalentes de objeto Objective-C.

También puede ser útil observar el diseño de Objective-C en sí. Es un poco diferente de C ++ en que el sistema de objetos se define en términos de funciones C, por ejemplo, objc_msg_send para llamar a un método en un objeto. El comstackdor traduce la syntax de corchetes en esas llamadas a funciones, por lo que no tiene que saberlo, pero teniendo en cuenta su pregunta, puede que le resulte útil aprender cómo funciona bajo el capó.

Hay varias técnicas que se pueden usar. El más importante es más cómo dividir el proyecto. Usamos una interfaz en nuestro proyecto que se declara en un archivo .h y la implementación del objeto en un archivo .c. La parte importante es que todos los módulos que incluyen el archivo .h ven solo un objeto como void * , y el archivo .c es el único que conoce las partes internas de la estructura.

Algo así para una clase que nombramos FOO como ejemplo:

En el archivo .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 

El archivo de implementación de C será algo así.

 #include  ... #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; } 

Entonces le doy el puntero explícitamente a un objeto para cada función de ese módulo. Un comstackdor de C ++ lo hace implícitamente, y en C lo escribimos explícitamente.

Realmente uso this en mis progtwigs, para asegurarme de que mi progtwig no comstack en C ++, y tiene la fina propiedad de estar en otro color en mi editor de resaltado de syntax.

Los campos de FOO_struct se pueden modificar en un módulo y no es necesario volver a comstackr ningún módulo para poder usarlo.

Con ese estilo ya manejo una gran parte de las ventajas de OOP (encapsulación de datos). Mediante el uso de indicadores de función, es incluso fácil implementar algo así como la herencia, pero, sinceramente, en realidad solo rara vez es útil.

Objeto orientado C, se puede hacer, he visto ese tipo de código en producción en Corea, y era el monstruo más horrible que había visto en años (fue como el año pasado (2007) cuando vi el código). Entonces sí, se puede hacer, y sí las personas lo han hecho antes, y aún lo hacen incluso en este día y edad. Pero recomendaría C ++ o Objective-C, ambos son lenguajes nacidos de C, con el propósito de proporcionar orientación a los objetos con diferentes paradigmas.

Si está convencido de que un enfoque OOP es superior para el problema que está tratando de resolver, ¿por qué estaría tratando de resolverlo con un lenguaje que no sea OOP? Parece que estás usando la herramienta incorrecta para el trabajo. Use C ++ o algún otro lenguaje de variantes de C orientado a objetos.

Si está preguntando porque está comenzando a codificar en un proyecto grande ya existente escrito en C, entonces no debe intentar forzar su propio (o el de otra persona) paradigmas de OOP en la infraestructura del proyecto. Siga las pautas que ya están presentes en el proyecto. En general, las API limpias y las bibliotecas y módulos aislados contribuirán en gran medida a tener un diseño limpio de OOP.

Si, después de todo esto, realmente está listo para hacer OOP C, lea esto (PDF).

Sí tu puedes. La gente escribía C orientada a objetos antes de que C ++ o Objective-C entraran en escena. Tanto C ++ como Objective-C fueron, en parte, bashs de tomar algunos de los conceptos OO utilizados en C y formalizarlos como parte del lenguaje.

Aquí hay un progtwig realmente simple que muestra cómo puede hacer algo que se parece a / es una llamada a método (hay mejores formas de hacerlo. Esto es solo una prueba de que el lenguaje admite los conceptos):

 #include 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; } 

Por supuesto, simplemente no será tan bonito como usar un lenguaje con soporte integrado. Incluso he escrito “ensamblador orientado a objetos”.

Un pequeño código OOC para agregar:

 #include  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; } 

Puede falsificarlo utilizando punteros de función, y de hecho, creo que es teóricamente posible comstackr progtwigs de C ++ en C.

Sin embargo, rara vez tiene sentido forzar un paradigma en un idioma en lugar de elegir un lenguaje que use un paradigma.

Estuve cavando esto por un año:

Como el sistema GObject es difícil de usar con C pura, traté de escribir algunas macros agradables para facilitar el estilo OO con 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; } 

Aquí está el sitio de mi proyecto (no tengo suficiente tiempo para escribir en. Doc, sin embargo, el documento en chino es mucho mejor).

OOC-GCC

Hay un ejemplo de herencia usando C en la charla de 1996 de Jim Larson dada en el Seminario de Progtwigción de la Hora de Almuerzo de la Sección 312 aquí: C de Alto y Bajo Nivel .

¿Qué artículos o libros son buenos para usar los conceptos de OOP en C?

Las interfaces C y las implementaciones de Dave Hanson son excelentes en encapsulación y asignación de nombres y muy buenas en el uso de punteros a funciones. Dave no intenta simular herencia.

OOP es solo un paradigma que coloca los datos como más importantes que el código en los progtwigs. OOP no es un lenguaje. Entonces, como la C simple es un lenguaje simple, OOP en la C simple también es simple.

Una cosa que quizás desee hacer es observar la implementación del kit de herramientas Xt para X Window . Claro que se está poniendo largo en el diente, pero muchas de las estructuras utilizadas fueron diseñadas para funcionar de manera OO dentro de la C. tradicional. Generalmente esto significa agregar una capa adicional de indirección aquí y allá y diseñar estructuras para colocarse una sobre la otra.

Realmente puede hacer muchas cosas en el camino de OO situado en C de esta manera, aunque algunas veces lo siente, los conceptos de OO no surgieron completamente formados por la mente de #include . Realmente constituyeron muchas de las mejores prácticas establecidas de la época. Los lenguajes y sistemas OO solo destilan y amplifican partes del espíritu de progtwigción del día.

La respuesta a la pregunta es ‘Sí, puedes’.

El kit C (OOC) orientado a objetos es para aquellos que desean progtwigr de una manera orientada a objetos, pero también se apega a la buena C antigua. OOC implementa clases, herencia única y múltiple, manejo de excepciones.

Caracteristicas

• Utiliza solo macros y funciones de C, ¡no se requieren extensiones de idioma! (ANSI-C)

• Código fuente fácil de leer para su aplicación. Se tuvo cuidado de hacer las cosas lo más simples posible.

• Herencia única de clases

• Herencia múltiple por interfaces y mixins (desde la versión 1.3)

• Implementando excepciones (¡en C puro!)

• Funciones virtuales para clases

• Herramienta externa para una fácil implementación de clases

Para obtener más detalles, visite http://ooc-coding.sourceforge.net/ .

Parece que la gente está tratando de emular el estilo C ++ utilizando C. Mi opinión es que hacer progtwigción orientada a objetos C realmente está haciendo una progtwigción orientada a las estructuras. Sin embargo, puede lograr cosas como vinculación tardía, encapsulación y herencia. Para la herencia, defina explícitamente un puntero a las estructuras base en su estructura secundaria y esto es obviamente una forma de herencia múltiple. También necesitarás determinar si tu

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

compile con c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj .

Entonces, el consejo es mantener un estilo puro de C y no intentar forzar un estilo C ++. También de esta manera se presta a una forma muy limpia de construir una API.

Consulte http://slkpg.byethost7.com/instance.html para obtener un giro más de OOP en C. Resalta los datos de instancia para la reentrada utilizando únicamente C nativa. La herencia múltiple se realiza manualmente utilizando envoltorios de funciones. La seguridad de tipo se mantiene. Aquí hay una pequeña muestra:

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

Llego un poco tarde a la fiesta, pero quiero compartir mi experiencia sobre el tema: actualmente trabajo con material incorporado, y el único comstackdor (confiable) que tengo es C, por lo que quiero aplicarlo orientado a objetos enfoque en mis proyectos integrados escritos en C.

La mayoría de las soluciones que he visto hasta ahora usan typecasts en gran medida, por lo que perdemos seguridad tipo: el comstackdor no lo ayudará si comete un error. Esto es completamente inaceptable.

Requisitos que tengo:

  • Evite los tipos de difusión tanto como sea posible, para que no perdamos seguridad de tipo;
  • Polimorfismo: deberíamos poder usar métodos virtuales, y el usuario de la clase no debería saber si algún método en particular es virtual o no;
  • Herencia múltiple: no la uso a menudo, pero a veces realmente quiero que algunas clases implementen múltiples interfaces (o extiendan múltiples superclases).

I’ve explained my approach in detail in this article: Object-oriented programming in C ; plus, there is an utility for autogeneration of boilerplate code for base and derived classes.

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.

I propose to use Objective-C, which is a superset of C.

While Objective-C is 30 years old, it allows to write elegant code.

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

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