¿Qué sería un conjunto de ingeniosos hacks de preprocesador (compatible con ANSI C89 / ISO C90) que permiten algún tipo de orientación de objeto feo (pero utilizable) en C?
Estoy familiarizado con algunos lenguajes diferentes orientados a objetos, así que no responda con respuestas como "¡Aprenda C ++!". He leído " Programación orientada a objetos con ANSI C " (cuidado: formato PDF ) y varias otras soluciones interesantes, ¡pero estoy interesado principalmente en la suya :-)!
Consulte también ¿Puede escribir código orientado a objetos en C?
Respuestas:
C Object System (COS) suena prometedor (todavía está en versión alfa). Trata de minimizar los conceptos disponibles en aras de la simplicidad y la flexibilidad: programación orientada a objetos uniforme que incluye clases abiertas, metaclases, metaclases de propiedad, genéricos, métodos múltiples, delegación, propiedad, excepciones, contratos y cierres. Hay un borrador (PDF) que lo describe.
La excepción en C es una implementación C89 de TRY-CATCH-FINALLY que se encuentra en otros lenguajes OO. Viene con una suite de prueba y algunos ejemplos.
Tanto por Laurent Deniau, que está trabajando mucho en la programación orientada a objetos en C .
fuente
Aconsejaría contra el uso del preprocesador (ab) para tratar de hacer que la sintaxis de C sea más parecida a la de otro lenguaje más orientado a objetos. En el nivel más básico, solo usa estructuras simples como objetos y las pasa por punteros:
Para obtener cosas como herencia y polimorfismo, debes trabajar un poco más duro. Puede hacer una herencia manual haciendo que el primer miembro de una estructura sea una instancia de la superclase, y luego puede lanzar punteros alrededor de las clases base y derivadas libremente:
Para obtener polimorfismo (es decir, funciones virtuales), utiliza punteros de función y, opcionalmente, tablas de puntero de función, también conocidas como tablas virtuales o vtables:
Y así es como se hace el polimorfismo en C. No es bonito, pero hace el trabajo. Hay algunos problemas difíciles relacionados con la conversión de punteros entre las clases base y derivada, que son seguros siempre que la clase base sea el primer miembro de la clase derivada. La herencia múltiple es mucho más difícil: en ese caso, para diferenciar entre clases base distintas de la primera, debe ajustar manualmente los punteros en función de las compensaciones adecuadas, lo que es realmente complicado y propenso a errores.
¡Otra cosa (difícil) que puede hacer es cambiar el tipo dinámico de un objeto en tiempo de ejecución! Simplemente reasigne un nuevo puntero vtable. Incluso puede cambiar selectivamente algunas de las funciones virtuales mientras mantiene otras, creando nuevos tipos híbridos. Solo tenga cuidado de crear una nueva vtable en lugar de modificar la vtable global, de lo contrario, afectará accidentalmente a todos los objetos de un tipo determinado.
fuente
struct derived {struct base super;};
es obvia para adivinar cómo funciona, ya que por orden de bytes es correcta.Una vez trabajé con una biblioteca C que se implementó de una manera que me pareció bastante elegante. Habían escrito, en C, una forma de definir objetos, luego heredaron de ellos para que fueran tan extensibles como un objeto C ++. La idea básica era esta:
La herencia es difícil de describir, pero básicamente fue esto:
Luego en otro archivo:
Entonces podría tener una camioneta creada en la memoria, y ser utilizada por un código que solo sabía sobre vehículos:
Funcionó maravillosamente, y los archivos .h definieron exactamente lo que debería poder hacer con cada objeto.
fuente
El escritorio GNOME para Linux está escrito en C orientado a objetos y tiene un modelo de objeto llamado " GObject " que admite propiedades, herencia, polimorfismo, así como algunas otras ventajas como referencias, manejo de eventos (llamadas "señales"), tiempo de ejecución mecanografía, datos privados, etc.
Incluye hacks de preprocesador para hacer cosas como la conversión de tipos en la jerarquía de clases, etc. Aquí hay una clase de ejemplo que escribí para GNOME (cosas como gchar son typedefs):
Fuente de clase
Encabezado de clase
Dentro de la estructura GObject hay un número entero GType que se utiliza como un número mágico para el sistema de escritura dinámica de GLib (puede convertir toda la estructura en un "GType" para encontrar su tipo).
fuente
Solía hacer este tipo de cosas en C, antes de saber qué era OOP.
El siguiente es un ejemplo, que implementa un búfer de datos que crece bajo demanda, dado un tamaño mínimo, incremento y tamaño máximo. Esta implementación particular se basó en "elementos", es decir, fue diseñada para permitir una colección tipo lista de cualquier tipo C, no solo un búfer de bytes de longitud variable.
La idea es que el objeto se instancia usando xxx_crt () y se elimina usando xxx_dlt (). Cada uno de los métodos "miembro" toma un puntero específicamente tipado para operar.
Implementé una lista vinculada, un búfer cíclico y otras cosas de esta manera.
Debo confesar que nunca he pensado en cómo implementar la herencia con este enfoque. Me imagino que una combinación de lo ofrecido por Kieveli podría ser un buen camino.
dtb.c:
dtb.h
PD: vint era simplemente un typedef de int: lo usé para recordarme que su longitud era variable de una plataforma a otra (para portar).
fuente
Ligeramente fuera de tema, pero el compilador original de C ++, Cfront , compiló C ++ a C y luego al ensamblador.
Preservado aquí .
fuente
Si piensa en los métodos invocados en los objetos como métodos estáticos que pasan un implícito '
this
' a la función, puede hacer que pensar OO en C sea más fácil.Por ejemplo:
se convierte en:
O algo así.
fuente
string->length(s);
ffmpeg (un kit de herramientas para el procesamiento de video) está escrito en C directo (y lenguaje ensamblador), pero usando un estilo orientado a objetos. Está lleno de estructuras con punteros de función. Hay un conjunto de funciones de fábrica que inicializan las estructuras con los punteros de "método" apropiados.
fuente
Si realmente piensa catefully, incluso estándar de uso de la biblioteca C POO - considerar
FILE *
como un ejemplo:fopen()
inicializa unFILE *
objeto, y lo usa utilizar métodos miembrosfscanf()
,fprintf()
,fread()
,fwrite()
y otros, y finalmente finalizar confclose()
.También puede usar la forma pseudo-Objetivo-C, que tampoco es difícil:
Usar:
Esto es lo que puede resultar de algún código Objective-C como este, si se usa un traductor Objective-C-to-C bastante antiguo:
fuente
__attribute__((constructor))
envoid __meta_Foo_init(void) __attribute__((constructor))
?popen(3)
también devuelve unFILE *
para otro ejemplo.Creo que lo que Adam Rosenfield publicó es la forma correcta de hacer OOP en C. Me gustaría agregar que lo que muestra es la implementación del objeto. En otras palabras, la implementación real se colocaría en el
.c
archivo, mientras que la interfaz se colocaría en el encabezado.h
archivo de . Por ejemplo, usando el ejemplo de mono anterior:La interfaz se vería así:
Puede ver en el
.h
archivo de interfaz que solo está definiendo prototipos. Luego puede compilar la parte de implementación ".c
archivo" en una biblioteca estática o dinámica. Esto crea la encapsulación y también puede cambiar la implementación a voluntad. El usuario de su objeto no necesita saber casi nada acerca de su implementación. Esto también pone el foco en el diseño general del objeto.Creo personalmente que oop es una forma de conceptualizar la estructura y la reutilización de su código y realmente no tiene nada que ver con esas otras cosas que se agregan a C ++, como la sobrecarga o las plantillas. Sí, esas son características útiles muy buenas, pero no son representativas de lo que realmente es la programación orientada a objetos.
fuente
typedef struct Monkey {} Monkey;
¿Cuál es el punto de escribirla después de haberla creado?struct _monkey
es simplemente un prototipo. La definición de tipo real se define en el archivo de implementación (el archivo .c). Esto crea el efecto de encapsulación y permite al desarrollador de API redefinir la estructura del mono en el futuro sin modificar la API. Los usuarios de la API solo deben preocuparse por los métodos reales. El diseñador de API se encarga de la implementación, incluida la forma en que se presenta el objeto / estructura. Por lo tanto, los detalles del objeto / estructura están ocultos para el usuario (un tipo opaco).int getCount(ObjectType obj)
etc. si elige definir la estructura en el archivo de implementación.Mi recomendación: que sea sencillo. Uno de los mayores problemas que tengo es el mantenimiento de software antiguo (a veces más de 10 años). Si el código no es simple, puede ser difícil. Sí, uno puede escribir OOP muy útil con polimorfismo en C, pero puede ser difícil de leer.
Prefiero objetos simples que encapsulan una funcionalidad bien definida. Un gran ejemplo de esto es GLIB2 , por ejemplo, una tabla hash:
Las claves son:
fuente
Si fuera a escribir OOP en CI probablemente iría con un pseudo- Pimpl diseño . En lugar de pasar punteros a estructuras, terminas pasando punteros a punteros a estructuras. Esto hace que el contenido sea opaco y facilita el polimorfismo y la herencia.
El verdadero problema con OOP en C es lo que sucede cuando las variables salen del alcance. No hay destructores generados por el compilador y eso puede causar problemas. Las macros posiblemente pueden ayudar, pero siempre será feo mirarlas.
fuente
if
declaraciones y su liberación al final. Por ejemploif ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }
Otra forma de programar en un estilo orientado a objetos con C es usar un generador de código que transforma un lenguaje específico de dominio en C. Como se hace con TypeScript y JavaScript para llevar OOP a js.
fuente
Salida:
Aquí hay un programa de lo que es la programación OO con C.
Esto es real, puro C, sin macros de preprocesador. Tenemos herencia, polimorfismo y encapsulación de datos (incluidos datos privados para clases u objetos). No hay posibilidad de que el calificador protegido sea equivalente, es decir, los datos privados también son privados en la cadena de herencia. Pero esto no es un inconveniente porque no creo que sea necesario.
CPolygon
no se instancia porque solo lo usamos para manipular objetos de la cadena de herencia que tienen aspectos comunes pero una implementación diferente de ellos (polimorfismo).fuente
@Adam Rosenfield tiene una muy buena explicación de cómo lograr OOP con C
Además, te recomendaría que leyeras
1) pjsip
Una muy buena biblioteca de C para VoIP. Puede aprender cómo logra OOP a través de estructuras y tablas de puntero de funciones
2) iOS Runtime
Aprenda cómo iOS Runtime impulsa el Objetivo C. Alcanza la POO a través de un puntero isa, metaclase
fuente
Para mí, la orientación de objetos en C debería tener estas características:
Encapsulación y ocultación de datos (se puede lograr utilizando estructuras / punteros opacos)
Herencia y soporte para polimorfismo (la herencia única se puede lograr usando estructuras; asegúrese de que la base abstracta no sea instanciable)
Funcionalidad de constructor y destructor (no es fácil de lograr)
Verificación de tipos (al menos para los tipos definidos por el usuario ya que C no impone ninguno)
Recuento de referencias (o algo para implementar RAII )
Soporte limitado para el manejo de excepciones (setjmp y longjmp)
Además de lo anterior, debe confiar en las especificaciones ANSI / ISO y no debe depender de la funcionalidad específica del compilador.
fuente
Mire http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html . Si nada más leer la documentación es una experiencia esclarecedora.
fuente
Llego un poco tarde a la fiesta aquí, pero me gusta evitar ambos extremos de macro: demasiados o demasiado código ofuscado, pero un par de macros obvias pueden hacer que el código OOP sea más fácil de desarrollar y leer:
Creo que esto tiene un buen equilibrio, y los errores que genera (al menos con las opciones predeterminadas de gcc 6.3) para algunos de los errores más probables son útiles en lugar de confusos. El punto es mejorar la productividad del programador, ¿no?
fuente
Si necesita escribir un pequeño código, intente esto: https://github.com/fulminati/class-framework
fuente
También estoy trabajando en esto basado en una solución macro. Supongo que es solo para los más valientes ;-) Pero ya es bastante agradable y ya estoy trabajando en algunos proyectos. Funciona para que primero defina un archivo de encabezado separado para cada clase. Me gusta esto:
Para implementar la clase, crea un archivo de encabezado y un archivo C donde implementa los métodos:
En el encabezado que creó para la clase, incluye otros encabezados que necesita y define los tipos, etc. relacionados con la clase. Tanto en el encabezado de la clase como en el archivo C incluye el archivo de especificación de la clase (consulte el primer ejemplo de código) y una macro X. Estas macros X ( 1 , 2 , 3 etc.) expandirán el código a las estructuras de clase reales y otras declaraciones.
Para heredar una clase
#define SUPER supername
y agregarsupername__define \
como la primera línea en la definición de clase. Ambos deben estar ahí. También hay soporte JSON, señales, clases abstractas, etc.Para crear un objeto, solo use
W_NEW(classname, .x=1, .y=2,...)
. La inicialización se basa en la inicialización de estructura introducida en C11. Funciona bien y todo lo que no está en la lista se establece en cero.Para llamar a un método, use
W_CALL(o,method)(1,2,3)
. Parece una llamada de función de orden superior, pero es solo una macro. Se expande a((o)->klass->method(o,1,2,3))
que es un truco realmente agradable.Ver documentación y el código en sí .
Como el marco necesita un código repetitivo, escribí un script de Perl (wobject) que hace el trabajo. Si usas eso, puedes escribir
y creará el archivo de especificación de clase, el encabezado de clase y un archivo C, que incluye
Point_impl.c
dónde implementa la clase. Ahorra bastante trabajo, si tiene muchas clases simples pero aún así todo está en C. wobject es un escáner basado en expresiones regulares muy simple que es fácil de adaptar a necesidades específicas, o reescribir desde cero.fuente
El proyecto de código abierto Dynace hace exactamente eso. Está en https://github.com/blakemcbride/Dynace
fuente
Puede probar COOP , un marco amigable para programadores para OOP en C, que presenta clases, excepciones, polimorfismo y administración de memoria (importante para el código incrustado). Es una sintaxis relativamente ligera, vea el tutorial en la Wiki allí.
fuente