2009-08-04 14 views
7

Sono particolarmente interessato agli oggetti che devono essere utilizzati all'interno di C, al contrario delle implementazioni di oggetti che formano il nucleo di linguaggi interpretati come Python.Quali tecniche/strategie usano le persone per costruire oggetti in C (non C++)?

+1

Anche se ho sempre usato quello che immagino si possa chiamare uno stile di programmazione C "object based", è molto, molto più facile a questo (e quasi a tutto il resto) in C++. Devo chiedere perché non sembra che tu voglia usarlo? –

+1

@Neil: se non ci fosse alcun motivo, non ci sarebbe alcun GObject. Ci sono molte ragioni per C: piattaforme obsolete, protezione, compatibilità ABI binaria, il tuo nome. – EFraim

+0

@EFraim Sono perfettamente a conoscenza di questi motivi, ma stavo chiedendo quali fossero le ragioni del questionario. –

risposta

4

Librerie come GObject.

Fondamentalmente GObject fornisce un modo comune per descrivere valori opachi (interi, stringhe) e oggetti (descrivendo manualmente l'interfaccia - come una struttura di puntatori di funzione, fondamentalmente correspoinding a un VTable in C++) - maggiori informazioni sulla struttura possono essere trovato nelle sue VTables reference

Si potrebbe spesso anche a mano implementare come in "COM in plain C"

+0

EFraim - Potresti descrivere la strategia di implementazione di GObject nella tua risposta in modo da poterla confrontare con alcune delle altre soluzioni fornite? – SetJmp

0

Guarda IJG's attuazione. Non usano solo setjmp/longjmp per la gestione delle eccezioni, hanno vtables e tutto. È una libreria ben scritta e abbastanza piccola da poter dare un ottimo esempio.

+0

Sai se stanno utilizzando un'implementazione come quella proposta da Dale e Falaina? O qualcosa di un po 'più dinamico? – SetJmp

+0

Sì, molto simile. – cheez

9

tendo a fare qualcosa di simile:

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

Con queste definizioni di strutture, il foo codice che implementa simile a questa:

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

Poi, nel codice che utilizza pippo:

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

Questo codice può essere riordinato con macro o funzioni inline in modo che appaia più simile a C

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

anche se si bastone con un tempo ragionevolmente breve nome per il campo "ops", semplicemente scrivendo il codice mostrato in origine non è particolarmente dettagliato.

Questa tecnica è del tutto adeguata per l'implementazione di un progetto basato su oggetti relativamente semplice in C, ma è non gestire i requisiti più avanzati come rappresentare esplicitamente le classi e l'ereditarietà del metodo. Per quelli, potresti aver bisogno di qualcosa come GObject (come menzionato da EFraim), ma ti suggerisco di assicurarti che tu abbia davvero bisogno delle funzionalità extra dei framework più complessi.

7

L'uso del termine "oggetti" è un po 'vago, quindi presumo che tu stia chiedendo come usare C per raggiungere determinati aspetti della programmazione orientata agli oggetti (sentiti libero di correggermi su questo presupposto .)

Metodo polimorfismo:

Metodo polimorfismo è tipicamente emulata in C usando puntatori a funzione. Per esempio, se ho avuto una struct che ho usato per rappresentare un image_scaler (cosa che richiede un'immagine e ridimensiona a nuove dimensioni), avrei potuto fare qualcosa di simile:

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

Poi, ho potuto fare diverse scaler di immagini come ad esempio:

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

Questo mi permette di ottenere un comportamento polimorfico per qualsiasi funzione che prende in un image_scaler e usa è il metodo scala semplicemente passando un image_scaler diverso.

Inheritance

L'ereditarietà è di solito realizzato in quanto tale:

struct base{ 
    int x; 
    int y; 
} 

struct derived{ 
    struct base; 
    int z; 
} 

Ora, io sono libero di utilizzare i campi extra di derivati, insieme con ottenere tutti i campi 'ereditato' di base. Inoltre, se si dispone di una funzione che accetta solo una struttura struct. puoi semplicemente lanciare il tuo struct dervied pointer in un puntatore struct base senza conseguenze

+0

Grazie Falain (ho upvoted). Potresti indicare una libreria che fa questo così io o chiunque altro possa esaminare un'intera implementazione? – SetJmp

0

Simile all'approccio di Dale ma un po 'più di un fucile a pedale è come PostgreSQL rappresenti internamente nodi di albero di analisi, tipi di espressioni e simili. Ci sono predefiniti Node e Expr struct, lungo le linee di

typedef struct { 
    NodeTag n; 
} Node; 

dove NodeTag è un typedef per unsigned int, e c'è un file di intestazione con un mazzo di costanti che descrivono tutti i possibili tipi di nodo. Nodi stessi aspetto:

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

e FooNode possono essere espressi in una Node impunemente, a causa di un capriccio di C struct: se due struct hanno identiche primi membri, possono essere espressi all'altro.

Sì, questo significa che è possibile trasmettere un FooNode a un BarNode, che probabilmente non si desidera eseguire. Se vuoi il corretto controllo del tipo di runtime, GObject è la strada da percorrere, anche se preparati a odiare la vita mentre stai imparando a farlo.

(nota:. Esempi dalla memoria, non hanno inciso sulle parti interne Postgres in un po 'Il developer FAQ ha più informazioni.)

+0

Molto tempo fa (circa 1988) ho lavorato su un compilatore C in cui i nodi di analisi erano un po 'uniti di singoli tipi di nodi, con un tag di tipo al di fuori dell'unione per decidere quale ramo dell'unione era valido: questo è moralmente equivalente a ciò tu descrivi. Oggi, quasi certamente lo farei sulla falsariga del mio suggerimento sopra. Trovo che l'uso di puntatori di funzione mi costringa a definire l'API pubblica desiderata - poiché il chiamante non conosce il nome della funzione che sta chiamando, è molto difficile che superi quella funzione per utilizzare i dati interni sull'implementazione. –

+0

Sigh ... s/bit union/big union/in sopra. Ci scusiamo per l'errore di battitura. –

+0

L'approccio del puntatore a funzione è decisamente superiore: per prima cosa, consente di approssimare i metodi di associazione all'oggetto. Mi piacciono i tuoi esempi e ho votato. –

2

Come si può vedere da navigare tutte le risposte, ci sono librerie, puntatori di funzioni, metodi di ereditarietà, incapsulamento, ecc., tutti disponibili (il C++ era originariamente un front-end per C).

Tuttavia, ho trovato che un aspetto MOLTO importante per il software è la leggibilità . Hai provato a leggere il codice di 10 anni fa? Di conseguenza , tendo a prendere l'approccio più semplice quando fare le cose come oggetti in C.

porre la seguente:

  1. È questo per un cliente con una scadenza (in tal caso, prendere in considerazione OOP) ?
  2. Posso utilizzare un OOP (spesso meno codice, più veloce da sviluppare, più leggibile)?
  3. Posso usare una libreria (codice esistente, modelli esistenti)?
  4. Sono limitato dalla memoria o dalla CPU (ad esempio Arduino)?
  5. Esiste un altro motivo tecnico per utilizzare C?
  6. Posso mantenere la mia C molto semplice e leggibile?
  7. Quali funzioni OOP ho veramente bisogno del mio progetto?

Io di solito tornare ad una cosa del genere l'API GLIB, che mi permette di incapsulare il mio codice e fornisce un'interfaccia molto leggibile. Se è necessario più , aggiungo i puntatori alle funzioni per il 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 
Problemi correlati