Progtwigción orientada a objetos en C

Posibles duplicados:
¿Puedes escribir código orientado a objetos en C?
Patrón orientado a objetos en C?

Recuerdo haber leído hace un tiempo sobre alguien (creo que era Linus Torvalds) hablando sobre cómo C ++ es un lenguaje horrible y cómo se puede escribir progtwigs orientados a objetos con C. Al tener tiempo para reflexionar, realmente no veo cómo todos los conceptos orientados a objetos se transfieren a C. Algunas cosas son bastante obvias. Por ejemplo:

  1. Para emular las funciones de miembro, puede poner punteros de función en estructuras.
  2. Para emular el polymorphism, puede escribir una función que tome una cantidad variable de argumentos y hacer algo de vudú según, por ejemplo, el sizeof del parámetro (s)

Sin embargo, ¿cómo emularías la encapsulación y la herencia?

Supongo que la encapsulación podría ser emulada por tener una estructura anidada que almacenaba miembros privados. Sería bastante fácil de usar, pero quizás podría llamarse PRIVATE o algo igualmente obvio para indicar que no está destinado a ser utilizado desde fuera de la estructura. ¿Y qué hay de la herencia?

Puede implementar polymorphism con funciones regulares y tablas virtuales (vtables). Este es un sistema bastante ingenioso que inventé (basado en C ++) para un ejercicio de progtwigción: texto alternativo

Los constructores asignan memoria y luego llaman a la función init de la clase donde se inicializa la memoria. Cada función init también debe contener una estructura vtable estática que contenga los punteros de función virtual (NULL para pura virtual). Las funciones de inicio de clase derivadas llaman a la función init de la superclase antes de hacer cualquier otra cosa.

Se puede crear una muy buena API implementando los envoltorios de funciones virtuales (que no deben confundirse con las funciones señaladas por los vtables) de la siguiente manera (agregue líneas static inline en static inline delante de él, si hace esto en el encabezado):

 int playerGuess(Player* this) { return this->vtable->guess(this); } 

La herencia individual se puede hacer abusando del diseño binario de una estructura: texto alternativo

Tenga en cuenta que la herencia múltiple es más desordenada, ya que a menudo necesita ajustar el valor del puntero al convertir entre los tipos de la jerarquía.

También se pueden agregar otros datos específicos de tipo a las tablas virtuales. Los ejemplos incluyen información del tipo de tiempo de ejecución (por ejemplo, nombre de tipo como una cadena), vincular a superclase vtable y la cadena de destructor. Probablemente desee destructores virtuales donde el destructor de clases derivado degrada el objeto a su superclase y luego llama recursivamente al destructor de ese y así sucesivamente, hasta que se alcanza el destructor de la clase base y eso finalmente libera la estructura.

La encapsulación se realizó definiendo las estructuras en player_protected.h e implementando las funciones (apuntadas por el vtable) en player_protected.c, y de forma similar para las clases derivadas, pero esto es bastante torpe y degrada el rendimiento (ya que los wrappers virtuales no se pueden poner a encabezados), así que recomendaría no hacerlo.

¿Has leído la “biblia” sobre el tema? Ver Object Oriented C …

Sin embargo, ¿cómo emularías la encapsulación y la herencia?

En realidad, la encapsulación es la parte más fácil. La encapsulación es una filosofía de diseño , no tiene nada que ver con el lenguaje ni con la forma en que piensas sobre los problemas.

Por ejemplo, la api de Windows FILE está completamente encapsulada. Cuando abre un archivo, obtiene un objeto opaco que contiene toda la información de estado para el archivo ‘objeto’. Usted devuelve este identificador a cada uno de los archivos io apis. La encapsulación es en realidad mucho mejor que C ++ porque no hay un archivo de encabezado público que las personas puedan ver y ver los nombres de sus variables privadas.

La herencia es más difícil, pero no es necesaria en absoluto para que su código esté orientado a objetos. De alguna manera, la agregación es mejor que la herencia de todos modos, y la agregación es igual de fácil en C que en C ++. ver esto por ejemplo.

En respuesta a Neil, ver Wikipedia para una explicación de por qué la herencia no es necesaria para el polymorphism.

Nosotros, los veteranos, escribimos código orientado a objetos años antes de que los comstackdores de C ++ estuvieran disponibles, es una forma de pensar, no un conjunto de herramientas.

El framework CoreFoundation basado en C de Apple se escribió en realidad para que sus “objetos” se pudieran duplicar como objetos en Objective-C, un lenguaje de OO real. Un subconjunto bastante grande del framework es de código abierto en el sitio de Apple como CF-Lite . Puede ser un estudio de caso útil en un marco de nivel de SO importante hecho de esta manera.

Desde una altitud un poco más alta y considerando que el problema es más abierto de lo que podría sugerir la stream principal OOP, la Progtwigción Orientada a Objetos significa pensar en objetos como datos con funciones asociadas. No necesariamente significa que una función tiene que estar físicamente unida a un objeto como lo es en los lenguajes populares que soportan el paradigma de OOP, por ejemplo en C ++:

 struct T { int data; int get_data() const { return data; } }; 

Sugeriría echar un vistazo más de cerca a GTK + Object y Type System . Es un shiny ejemplo de OOP realizado en lenguaje de progtwigción C:

GTK + implementa su propio sistema de objetos personalizados, que ofrece funciones estándar orientadas a objetos, como la herencia y la función virtual

La asociación también puede ser contractual y convencional.

Con respecto a las técnicas de encapsulación y ocultación de datos, popular y simple puede ser un puntero opaco (o tipo de datos opaco): puede pasarlo, pero para cargar o almacenar cualquier información, debe llamar a una función asociada que sepa cómo hablar con el objeto escondido detrás de ese puntero opaco.

Otro, similar pero diferente es el tipo de datos de sombra : consulte este enlace donde Jon Jagger explica de forma excelente esta técnica no tan conocida.

las bibliotecas gtk y glib usan macros para lanzar objetos a varios tipos.
add_widget (GTK_WIDGET (myButton));
No puedo decir cómo se hace, pero puede leer su fuente para descubrir exactamente cómo se hace.

Eche un vistazo a la forma en que la capa de VFS funciona en el kernel de Linux para un ejemplo de un patrón de herencia. Las operaciones de archivos para los diversos sistemas de archivos “heredan” un conjunto de funciones genéricas de operaciones de archivos (por ejemplo, generic_file_aio_read() , generic_file_llseek() …), pero pueden anularlas con sus propias implementaciones (por ejemplo, ntfs_file_aio_write() ).

Definitivamente mira a Objective-C.

 typedef struct objc_object { Class isa; } *id; typedef struct objc_class { struct objc_class *isa; struct objc_class *super_class const char *name; long version; long info long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols; } *Class; 

Como puede ver, la información de herencia, junto con otros detalles, se mantiene en una estructura de clase (convenientemente la clase también se puede tratar como un objeto).

Objective-C sufre de la misma manera que C ++ con encapsulamiento en el que necesita declarar públicamente sus variables. Straight C es mucho más flexible, ya que solo puede devolver punteros vacíos a los que solo su módulo tiene acceso interno, por lo que, en ese aspecto, la encapsulación es mucho mejor.

Una vez escribí un progtwig básico de pintura C estilo OO como parte de un curso de gráficos. No llegué tan lejos como la statement de clase, simplemente usé un puntero vtable como el primer elemento de la estructura e implementé la herencia codificada a mano. Lo bueno de jugar con los vtables a un nivel tan bajo es que puede cambiar el comportamiento de la clase en tiempo de ejecución cambiando algunos punteros o cambiando dinámicamente la clase de los objetos. Fue bastante fácil crear todo tipo de objetos híbridos, herencia múltiple falsa, etc.

Buen artículo y discusión sobre Objective-C aquí:

http://cocoawithlove.com/2009/10/objective-c-niche-why-it-survives-in.html

Para un gran ejemplo de progtwigción orientada a objetos en C, mira la fuente de POV-Ray de hace varios años: la versión 3.1g es particularmente buena. Los “objetos” eran struct con indicadores de función, por supuesto. Las macros se usaron para proporcionar los métodos centrales y los datos para un objeto abstracto, y las clases derivadas fueron estructuras que comenzaron con esa macro. Sin embargo, no hubo ningún bash de tratar con lo privado / público. Las cosas que se veían estaban en archivos .h y los detalles de implementación estaban en archivos .c, en su mayoría, excepto por muchas excepciones.

Hubo algunos trucos ingeniosos que no veo cómo podrían trasladarse a C ++, como la conversión de una clase a una diferente pero similar sobre la marcha con la reasignación de punteros a funciones. Simple para los lenguajes dynamics de hoy. Olvidé los detalles; Creo que podría haber sido intersección CSG y objetos de unión.

http://www.povray.org/

Un poco de historia interesante. Cfront , el código C de la implementación original de C ++ y luego requiere un comstackdor de C para construir el código final. Entonces, cualquier cosa que pueda expressse en C ++ podría escribirse como C.

Una forma de manejar la herencia es tener estructuras anidadas:

 struct base { ... }; void method_of_base(base *b, ...); struct child { struct base base_elements; ... }; 

A continuación, puede hacer llamadas como esta:

 struct child c; method_of_base(&c.b, ...); 

Es posible que desee ver Objective-C, eso es más o menos lo que hace. Es solo un front-end que comstack el código OO de Objective-C a C.