2009-08-04 13 views
7

Me interesan especialmente los objetos destinados a ser utilizados desde C, en oposición a las implementaciones de objetos que forman el núcleo de lenguajes interpretados como python.¿Qué técnicas/estrategias usan las personas para construir objetos en C (no en C++)?

+1

A pesar de que siempre he usado lo que supongo que se podría llamar un "objeto basado en" estilo de programación C, que es mucho, mucho más fácil a esto (y casi todo lo demás) en C++. Tengo que preguntar por qué parece que no quieres usarlo? –

+1

@Neil: Si no hubiera ninguna razón, no habría GObject. Hay muchas razones para C: plataformas obsoletas, protabilidad, compatibilidad ABI binaria, lo que sea. – EFraim

+0

@EFraim Soy muy consciente de esas razones, pero estaba preguntando cuáles eran las razones del interrogador. –

Respuesta

4

Bibliotecas como GObject.

Básicamente GObject proporciona una forma común de describir valores opacos (enteros, cadenas) y objetos (describiendo manualmente la interfaz - como una estructura de punteros de función, básicamente correspoving a una tabla VT en C++) - se puede obtener más información sobre la estructura encontrado en sus reference

Se podría menudo también a mano implementar vtables como en "COM in plain C"

+0

EFraim: ¿podría describir la estrategia de implementación de GObject en su respuesta para que podamos compararla con algunas de las otras soluciones que se ofrecen? – SetJmp

0

Mira IJG's aplicación. No solo usan setjmp/longjmp para manejo de excepciones, tienen tablas virtuales y todo. Es una biblioteca bien escrita y lo suficientemente pequeña como para que pueda obtener un buen ejemplo.

+0

¿Sabes si están usando una implementación como la sugerida por Dale y Falaina? ¿O algo un poco más dinámico? – SetJmp

+0

Sí, muy similar. – cheez

9

que tienden a hacer algo como esto:

struct foo_ops { 
    void (*blah)(struct foo *, ...); 
    void (*plugh)(struct foo *, ...); 
}; 
struct foo { 
    struct foo_ops *ops; 
    /* data fields for foo go here */ 
}; 

Con estas definiciones de la estructura, el foo código de la aplicación se ve algo como esto:

static void plugh(struct foo *, ...) { ... } 
static void blah(struct foo *, ...) { ... } 

static struct foo_ops foo_ops = { blah, plugh }; 

struct foo *new_foo(...) { 
    struct foo *foop = malloc(sizeof(*foop)); 
    foop->ops = &foo_ops; 
    /* fill in rest of *foop */ 
    return foop; 
} 

Luego, en el código que utiliza foo:

struct foo *foop = new_foo(...); 
foop->ops->blah(foop, ...); 
foop->ops->plugh(foop, ...); 

Este código se puede arreglar con macros o funciones en línea para que se vea más como C-

foo_blah(foop, ...); 
foo_plugh(foop, ...); 

aunque si usted se pega con un nombre razonablemente corto para el campo "ops", sólo tiene que escribir el código que se muestra inicialmente no es particularmente detallado.

Esta técnica es completamente adecuado para la implementación de un relativamente simples diseños basados ​​en objetos en C, pero lo hace no manejar los requerimientos más avanzados, tales como clases que representan de forma explícita, y la herencia método. Para ellos, es posible que necesite algo como GObject (como menciona EFraim), pero le sugiero que se asegure de que realmente necesita las características adicionales de los marcos más complejos.

7

Su uso del término "objetos" es un tanto vago, así que supongo que está preguntando cómo usar C para lograr ciertos aspectos de la Programación Orientada a Objetos (no dude en corregirme en esta suposición .)

Método polimorfismo:

polimorfismo Método está típicamente emulado en C usando los punteros de función. Por ejemplo, si tuviera una estructura que utiliza para representar una image_scaler (algo que toma una imagen y cambia el tamaño a las nuevas dimensiones), que podía hacer algo como esto:

struct image_scaler { 
    //member variables 
    int (*scale)(int, int, int*); 
} 

Entonces, pude hacer varios escaladores imagen tales como:

struct image_scaler nn, bilinear; 
nn->scale = &nearest_neighbor_scale; 
bilinear->scale = &bilinear_scale; 

Esto me permite lograr un comportamiento polimórfico para cualquier función que toma en un image_scaler y utiliza su método de escala simplemente pasando un image_scaler diferente.

Herencia

La herencia se consigue normalmente como tal:

struct base{ 
    int x; 
    int y; 
} 

struct derived{ 
    struct base; 
    int z; 
} 

Ahora, yo soy libre de utilizar campos adicionales de derivados, además de hacer todos los campos 'heredadas' de base. Además, si tiene una función que solo toma en una base de estructura. simplemente se puede convertir el puntero struct dervied en un puntero de struct struct sin consecuencias

+0

Gracias Falain (he votado arriba). ¿Podría apuntar a una biblioteca que hace esto para que yo o cualquier otra persona pueda examinar toda una implementación? – SetJmp

0

Similar al enfoque de Dale, pero un poco más de un footgun es cómo PostgreSQL representa los nodos del árbol de análisis sintáctico, tipos de expresiones y similares internamente. Hay por defecto Node y Expr estructuras, a lo largo de las líneas de

typedef struct { 
    NodeTag n; 
} Node; 

donde NodeTag es un typedef para unsigned int, y hay un archivo de cabecera con un grupo de constantes que describen todos los posibles tipos de nodos. Los nodos ellos mismos se ven así:

typedef struct { 
    NodeTag n = FOO_NODE; 
    /* other members go here */ 
} FooNode; 

y una FooNode se pueden echar a un Node con impunidad, debido a una peculiaridad de estructuras C: si dos estructuras tienen primeros elementos idénticos, pueden ser fundido entre sí.

Sí, esto significa que un FooNode se puede convertir a BarNode, lo que probablemente no desee hacer. Si desea una verificación de tipo de tiempo de ejecución adecuada, GObject es el camino a seguir, aunque prepárese para odiar la vida mientras la domina.

(nota:. Ejemplos de la memoria, no se han cortado en la parte interna de Postgres en un tiempo El developer FAQ tiene más información.)

+0

Hace mucho tiempo (alrededor de 1988) trabajé en un compilador de C donde los nodos de análisis eran un poco de unión de tipos de nodos individuales, con una etiqueta de tipo fuera del sindicato para decidir qué rama de la unión era válida: esto es moralmente equivalente a tú describes. Hoy, casi seguro lo haré en la línea de mi sugerencia anterior. Encuentro que el uso de punteros de función realmente me obliga a definir la API pública deseada, ya que la persona que llama no conoce el nombre de la función a la que llama, es muy difícil llegar más allá de esa función para usar datos internos sobre la implementación. –

+0

suspiro ... s/unión de bit/unión grande/en lo de arriba. Lo siento por el error tipográfico. –

+0

El enfoque del puntero a la función es definitivamente superior; por un lado, le permite aproximar los métodos de enlace al objeto. Me gustan tus ejemplos y lo voté. –

2

Como se puede ver de la navegación por todas las respuestas, hay bibliotecas, punteros de función, medios de herencia, encapsulación, etc., todo disponible (C++ fue originalmente un front-end para C).

Sin embargo, he encontrado que un aspecto MUY importante para el software es la legibilidad . ¿Has intentado leer el código de hace 10 años? Como resultado , tiendo a tomar el enfoque más simple al hacer cosas como objetos en C.

preguntar lo siguiente:

  1. Es esto para un cliente con un plazo (si es así, considere programación orientada a objetos) ?
  2. ¿Puedo usar un OOP (a menudo menos código, más rápido de desarrollar, más legible)?
  3. ¿Puedo utilizar una biblioteca (código existente, plantillas existentes)?
  4. ¿Estoy limitado por la memoria o la CPU (por ejemplo, Arduino)?
  5. ¿Hay alguna otra razón técnica para usar C?
  6. ¿Puedo mantener mi C muy simple y legible?
  7. ¿Qué características de POO realmente necesito para mi proyecto?

Normalmente vuelvo a algo así como la API GLIB que me permite encapsular mi código y proporciona una interfaz muy legible. Si se necesita más , agrego indicadores de función para polimorfismo.

class_A.h: 
    typedef struct _class_A {...} Class_A; 
    Class_A* Class_A_new(); 
    void Class_A_empty(); 
    ... 

#include "class_A.h" 
Class_A* my_instance; 
my_instance = Class_A_new(); 
my_instance->Class_A_empty(); // can override using function pointers 
Cuestiones relacionadas