"Si realmente quieres azúcar OO, ve a usar C ++" , fue la respuesta inmediata que recibí de uno de mis amigos cuando le pregunté esto. Sé que dos cosas están muy mal aquí. Primero OO NO es 'azúcar', y segundo, C ++ NO ha absorbido C.
Necesitamos escribir un servidor en C (el front-end que estará en Python), por lo que estoy explorando mejores formas de administrar grandes programas en C.
El modelado de un sistema grande en términos de objetos e interacciones de objetos lo hace más manejable, mantenible y extensible. Pero cuando intenta traducir este modelo a C que no contiene objetos (y todo lo demás), se enfrenta a algunas decisiones importantes.
¿Crea una biblioteca personalizada para proporcionar las abstracciones OO que necesita su sistema? Cosas como objetos, encapsulación, herencia, polimorfismo, excepciones, pub / sub (eventos / señales), espacios de nombres, introspección, etc. (por ejemplo, GObject o COS ).
O simplemente use las construcciones básicas de C ( struct
y funciones) para aproximar todas sus clases de objetos (y otras abstracciones) de manera ad-hoc. (por ejemplo, algunas de las respuestas a esta pregunta sobre SO )
El primer enfoque le brinda una forma estructurada de implementar todo su modelo en C. Pero también agrega una capa de complejidad que debe mantener. (Recuerde, la complejidad era lo que queríamos reducir mediante el uso de objetos en primer lugar).
No sé sobre el segundo enfoque, y qué tan efectivo es para aproximar todas las abstracciones que pueda necesitar.
Entonces, mis preguntas simples son: ¿Cuáles son las mejores prácticas para realizar un diseño orientado a objetos en C. Recuerde que no estoy preguntando CÓMO hacerlo. Esta y esta pregunta hablan de ello, e incluso hay un libro sobre esto. Lo que más me interesa son algunos consejos / ejemplos realistas que aborden los problemas reales que aparecen cuando dong esto.
Nota: por favor no aconseje por qué C no debe usarse a favor de C ++. Ya pasamos esa etapa.
fuente
extern "C"
y pueda usarse desde python. Puede hacerlo manualmente o puede hacer que SWIG lo ayude con eso. Por lo tanto, el deseo de la interfaz de Python no es motivo para no usar C ++. Eso no quiere decir que no haya razones válidas para querer quedarse con C.Respuestas:
De mi respuesta a ¿Cómo debo estructurar proyectos complejos en C (no OO sino sobre la gestión de la complejidad en C):
De mi respuesta a ¿Cuáles son las convenciones de nomenclatura típicas para las funciones públicas y privadas de OO C (creo que esta es una mejor práctica):
Además, muchas herramientas UML pueden generar código C a partir de diagramas UML. Una de código abierto es Topcased .
fuente
Creo que necesita diferenciar OO y C ++ en esta discusión.
Implementar objetos es posible en C, y es bastante fácil: solo cree estructuras con punteros de función. Ese es su "segundo enfoque", y yo lo seguiría. Otra opción sería no utilizar punteros de función en la estructura, sino pasar la estructura de datos a las funciones en llamadas directas, como un puntero de "contexto". Eso es mejor, en mi humilde opinión, ya que es más legible, más fácil de rastrear y permite agregar funciones sin cambiar la estructura (fácil de heredar si no se agregan datos). De hecho, así es como C ++ generalmente implementa el
this
puntero.El polimorfismo se vuelve más complicado porque no hay herencia incorporada y soporte de abstracción, por lo que tendrá que incluir la estructura principal en su clase secundaria o hacer muchas pastas de copia, cualquiera de esas opciones es francamente horrible, aunque técnicamente sencillo. Enorme cantidad de errores esperando a suceder.
Las funciones virtuales se pueden lograr fácilmente a través de punteros de función que apuntan a diferentes funciones según sea necesario, una vez más, muy propensos a errores cuando se realizan manualmente, una gran cantidad de trabajo tedioso para inicializar esos punteros correctamente.
En cuanto a los espacios de nombres, excepciones, plantillas, etc., creo que si está limitado a C, debería renunciar a ellos. He trabajado en escribir OO en C, no lo haría si tuviera una opción (en ese lugar de trabajo, literalmente luché para introducir C ++, y los gerentes estaban "sorprendidos" de lo fácil que era integrarlo con el resto de los módulos C al final).
No hace falta decir que si puede usar C ++, use C ++. No hay una razón real para no hacerlo.
fuente
Aquí están los conceptos básicos de cómo crear orientación de objeto en C
1. Crear objetos y encapsular
Por lo general, uno crea un objeto como
object_instance = create_object_typex(parameter);
Los métodos se pueden definir en una de las dos formas aquí.
Tenga en cuenta que en la mayoría de los casos,
object_instance (or object_instance_private_data)
se devuelve es de tipovoid *.
La aplicación no puede hacer referencia a miembros individuales o funciones de este.Además de esto, cada método utiliza estas instancia_objeto para el método posterior.
2. Polimorfismo
Podemos usar muchas funciones y punteros de función para anular ciertas funciones en tiempo de ejecución.
por ejemplo, todos los métodos_objetos se definen como un puntero de función que se puede extender a los métodos públicos y privados.
También podemos aplicar la sobrecarga de funciones en un sentido limitado, mediante el uso de
var_args
que es muy similar a la cantidad variable de argumentos definidos en printf. sí, esto no es tan flexible en C ++, pero esta es la forma más cercana.3. Definiendo herencia
Definir la herencia es un poco complicado, pero se puede hacer lo siguiente con las estructuras.
aquí el
doctor
yengineer
se deriva de la persona y es posible escribirlo a un nivel superior, digamosperson
.El mejor ejemplo de esto se usa en GObject y los objetos derivados de él.
4. Creación de clases virtuales Cito un ejemplo de la vida real de una biblioteca llamada libjpeg utilizada por todos los navegadores para la decodificación de jpeg. Crea una clase virtual llamada error_manager que la aplicación puede crear instancias concretas y suministrar de nuevo:
Tenga en cuenta que JMETHOD se expande en un puntero de función a través de una macro que debe cargarse con los métodos correctos, respectivamente.
He tratado de decir tantas cosas sin demasiadas explicaciones individuales. Pero espero que la gente pueda probar sus propias cosas. Sin embargo, mi intención es solo mostrar cómo se mapean las cosas.
Además, habrá muchos argumentos de que esto no será exactamente una propiedad verdadera del equivalente de C ++. Sé que OO en C no será tan estricto con su definición. Pero trabajando así se entenderían algunos de los principios básicos.
Lo importante no es que OO sea tan estricto como en C ++ y JAVA. Es que uno puede organizar estructuralmente el código con OO pensando y operarlo de esa manera.
Recomiendo encarecidamente a las personas que vean el diseño real de libjpeg y los siguientes recursos
a. Programación orientada a objetos en C
b. Este es un buen lugar donde las personas intercambian ideas
c. y aquí está el libro completo
fuente
La orientación a objetos se reduce a tres cosas:
1) Diseño de programa modular con clases autónomas.
2) Protección de datos con encapsulación privada.
3) Herencia / polimorfismo y varias otras sintaxis útiles como constructores / destructores, plantillas, etc.
1 es el más importante con diferencia, y también es completamente independiente del idioma, se trata del diseño del programa. En C lo hace creando "módulos de código" autónomos que consisten en un archivo .h y un archivo .c. Considera esto como el equivalente de una clase OO. Puede decidir qué se debe colocar dentro de este módulo a través del sentido común, UML o cualquier método de diseño OO que esté utilizando para programas C ++.
2 también es bastante importante, no solo para proteger contra el acceso intencional a datos privados, sino también para proteger contra el acceso no intencional, es decir, "desorden del espacio de nombres". C ++ hace esto de formas más elegantes que C, pero aún se puede lograr en C usando la palabra clave estática. Todas las variables que habría declarado como privadas en una clase de C ++ deberían declararse como estáticas en C y ubicarse en el alcance del archivo. Solo son accesibles desde su propio módulo de código (clase). Puede escribir "setters / getters" tal como lo haría en C ++.
3 es útil pero no necesario. Puede escribir programas OO sin herencia o sin constructores / destructores. Es bueno tener estas cosas, ciertamente pueden hacer que los programas sean más elegantes y quizás también más seguros (o lo contrario si se usan descuidadamente). Pero no son necesarios. Como C no admite ninguna de estas útiles funciones, simplemente tendrá que prescindir de ellas. Los constructores pueden ser reemplazados por funciones init / destruct.
La herencia se puede hacer a través de varios trucos de estructura, pero le aconsejaría que no lo haga, ya que probablemente hará que su programa sea más complejo sin ninguna ganancia (la herencia en general debe aplicarse con cuidado, no solo en C sino en cualquier lenguaje).
Finalmente, cada truco de OO se puede hacer en el libro de C. Axel-Tobias Schreiner "Programación orientada a objetos con ANSI C" de principios de los 90 lo demuestra. Sin embargo, no recomendaría ese libro a nadie: agrega una complejidad desagradable y extraña a sus programas en C que simplemente no vale la pena. (El libro está disponible de forma gratuita aquí para aquellos aún interesados a pesar de mi advertencia).
Entonces, mi consejo es implementar 1) y 2) arriba y omitir el resto. Es una forma de escribir programas en C que ha demostrado tener éxito durante más de 20 años.
fuente
Tomando prestada algo de experiencia de los diversos tiempos de ejecución de Objective-C, escribir una capacidad de OO dinámica y polimórfica en C no es demasiado difícil (por otro lado, hacer que sea rápido y fácil de usar, parece continuar después de 25 años). Sin embargo, si implementa una capacidad de objeto de estilo Objective-C sin extender la sintaxis del lenguaje, entonces el código con el que termina es bastante desordenado:
objc_msgSend(object, selector, …)
. Al saber de qué clase es una instancia el objeto, puede encontrar la implementación que coincida con el selector y así ejecutar la función correcta.Todo eso es parte de una biblioteca OO de propósito general diseñada para permitir que múltiples desarrolladores usen y amplíen las clases de los demás, por lo que podría ser excesivo para su propio proyecto. A menudo he diseñado proyectos C como proyectos orientados a clases "estáticas" utilizando estructuras y funciones: - cada clase es la definición de una estructura C que especifica el diseño ivar - cada instancia es solo una instancia de la estructura correspondiente - los objetos no pueden ser "mensajeado", pero las funciones de método similar a
MyClass_doSomething(struct MyClass *object, …)
se definen . Esto aclara las cosas en el código que el enfoque ObjC pero tiene menos flexibilidad.Dónde intercambiar mentiras depende de su propio proyecto: parece que otros programadores no usarán sus interfaces C, por lo que la elección se reduce a las preferencias internas. Por supuesto, si decides que quieres algo como la biblioteca objc runtime, entonces hay bibliotecas multiplataforma objc runtime que servirán.
fuente
GObject realmente no oculta ninguna complejidad e introduce algo de complejidad propia. Diría que lo ad-hoc es más fácil que GObject a menos que necesite las cosas avanzadas de GObject como señales o la maquinaria de interfaz.
La cosa es ligeramente diferente con COS ya que viene con un preprocesador que extiende la sintaxis C con algunas construcciones OO. Hay un preprocesador similar para GObject, el G Object Builder .
También puede probar el lenguaje de programación Vala , que es un lenguaje completo de alto nivel que se compila en C y de una manera que permite usar las bibliotecas Vala a partir de código C simple. Puede usar GObject, su propio marco de objetos o la forma ad-hoc (con características limitadas).
fuente
En primer lugar, a menos que sea tarea o no haya un compilador de C ++ para el dispositivo de destino, no creo que tenga que usar C, puede proporcionar una interfaz C con enlace C desde C ++ con bastante facilidad.
En segundo lugar, vería cuánto dependerá del polimorfismo y las excepciones y las otras características que los marcos pueden proporcionar, si no es mucho más simple, las estructuras con funciones relacionadas serán mucho más fáciles que un marco completo, si una porción significativa de su el diseño los necesita, luego muerde la viñeta y usa un marco para que no tenga que implementar las características usted mismo.
Si todavía no tiene un diseño para tomar la decisión, haga un pico y vea lo que le dice el código.
Por último, no tiene por qué ser una opción u otra (aunque si va marco desde el principio, posiblemente también pueda seguir con él), debería ser posible comenzar con estructuras simples para las partes simples y solo agregar bibliotecas según sea necesario.
EDITAR: Por lo tanto, es una decisión de la gerencia correcta ignoremos el primer punto entonces.
fuente