2010-09-29 11 views
11

In C++ e Java, le strutture dati possono avere private, public e protected regioni. Mi piacerebbe portare questo concetto in un programma in lingua C che sto scrivendo.Partizionare la struttura in sezioni private e pubbliche?

Esistono idiomi per l'implementazione di puntatori di funzioni e campi dati privati ​​o protetti in un C struct? So che C struct s sono pubblici, sto cercando un idioma per nascondere alcuni dettagli di implementazione e costringere gli utenti a utilizzare l'interfaccia pubblica.

Nota: La lingua è stato scelto dal negozio, così mi sono bloccato attuazione concetti Object Oriented in C.

Grazie.

+0

Il nascondiglio e l'incapsulamento delle informazioni precedono l'OOD. –

risposta

20

Come sai, non puoi farlo. Tuttavia, ci sono idiomi che permetteranno un effetto simile.

C ti permetterà di fare qualcosa di simile a quello che è noto come l'idioma "pimpl" nella progettazione orientata agli oggetti. La tua struct può avere un puntatore opaco a un'altra struct dichiarata in avanti che funge da dati privati ​​della struct.Le funzioni che operano sulla struttura, prendendo il posto delle funzioni membro, possono avere la definizione completa per il membro privato e possono farne uso, mentre altre parti del codice non possono. Ad esempio:

Espigatrice, foo.h:

struct FooPrivate; 

    struct Foo { 
    /* public: */ 
     int x; 
     double y; 
    /* private: */ 
     struct FooPrivate* p; 
    }; 

    extern struct Foo* Foo_Create(); /* "constructor" */ 

    extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */ 

Nell'implementazione, foo.c:

struct FooPrivate { 
    int z; 
    }; 

    struct Foo* Foo_Create() 
    { 
    struct Foo* foo = malloc(sizeof(Foo)); 

    foo->p = malloc(sizeof(FooPrivate)); 

    foo->x = 0; 
    foo->y = 0; 
    foo->p->z = 0; 

    return foo; 
    } 

    void Foo_DoWhatever(struct Foo* foo) 
    { 
     foo->p->z = 4; /* Can access "private" parts of foo */ 
    } 

In un programma:

#include "foo.h" 

    int main() 
    { 
     struct Foo* foo = Foo_Create(); 

     foo->x = 100; /* Can access "public" parts of foo */ 
     foo->p->z = 20; /* Error! FooPrivate is not fully declared here! */ 

     Foo_DoWhatever(foo); /* Can call "member" function */ 

     return 0; 
    } 

Nota la è necessario utilizzare una funzione di "costruzione" per allocare memoria per i dati privati. Ovviamente è necessario accoppiarlo con una speciale funzione di "distruttore" per deallocare correttamente i dati privati.

O, in alternativa, se si vuole che il proprio struct non avere campi pubblici di sorta, si potrebbe creare lo struct intera opaca, e solo l'intestazione essere qualcosa come

struct Foo; 

    extern struct Foo* Foo_Create(); /* "constructor" */ 

    extern void Foo_DoWhatever(struct Foo* foo); /* "member function" */ 

Con la definizione stessa di struct Foo in foo.c e le funzioni getter e setter disponibili per qualsiasi proprietà a cui si desidera fornire l'accesso diretto.

+0

+1 Grazie per avermi ricordato l'idioma di Pimple. Non ho mai pensato di applicarlo al linguaggio C. –

+2

In C99, potresti prendere in considerazione la possibilità di sostituire il puntatore a 'struct FooPrivate' con un membro di array flessibile' struct FooPrivate p [] '. Ciò impedirà all'utente di eseguire accidentalmente una copia superficiale di 'struct Foo', poiché renderà' struct Foo' un tipo incompleto. – caf

+0

@caf È possibile espandere le modifiche necessarie per utilizzare invece i membri di array flessibili? – CMCDragonkai

0

Mi dispiace per te, perché schiacciare diversi concetti OO in C è spesso come un piolo quadrato in un buco rotondo. Detto questo, GObject ha il supporto per membri pubblici e privati, ma è una delle mie architetture meno favorite sulla terra. Se non ti interessa il risultato minore delle prestazioni, potresti essere in grado di fare una soluzione più semplice - avere una struttura secondaria piena di membri privati ​​e avere un puntatore anonimo a quella struttura dalla struttura primaria (pubblica).

+1

Mi piacerebbe stare lontano dai pacchetti di libreria in quanto ciò comporta problemi di licenza e lo sviluppo deve essere completato rapidamente. –

+0

(a) GObject è una libreria open-source, anche se ammesso che possa presentare problemi di licenza; (b) la struttura privata secondaria non richiede una libreria. – Reinderien

9

Il concetto talvolta usato in C è

// lib.h 
typedef struct { 
    int publicInt; 
    //... 
    char * publicStr; 
} Public; 

Public * getPublic(); 
int function(Public * public); 

// lib.c 

typedef struct { 
    Public public; 
    int privateInt; 
    // ... 
    char * privateStr 
} Private; 

static Private * getPrivate(); 

Public * getPublic() { return (Public*) getPrivate(); } 
int function(Public * public) { 
    Private * private = (Private *) public; 
    // ... 
} 

Questo utilizza il trucco standard che un puntatore a una struct è intercambiabile con un puntatore al primo elemento in una struttura.

Se volete tutti tuoi campi da privati, è ancora più facile:

// lib2.h 
typedef struct AllPrivate * Handle; 
Handle getHandle(); 
int function2(Handle handle); 

// lib2.c 
struct AllPrivate { /* ... */ } 

I file che #include lib2.h non si lamenterà, poiché usiamo soltanto struct AllPrivate *, e tutti i puntatori sono gli stessi dimensione, quindi il compilatore non ha bisogno di conoscere le interiora di struct AllPrivate.

Per fare una regione protetta, si era appena necessario definire

// include/public.h 
struct Public { /* ... */ } 
struct Public * getPublic(); 
int somePublicFunction(struct Public *); 

// dev/include/protected.h 
struct Protected { struct Public public; /* ... */ } 
struct Protected * getProtected(); 
int someProtectedFunction(struct Protected *); 

// dev/src/private.c 
struct Private { struct Protected protected; /* ... * /} 
struct Public * getPublic() { return (struct Public *) getPrivate(); } 
struct Public * getProtected() { return (struct Protected *) getPrivate(); } 
int somePublicFunction(struct Public * public) { 
    struct Private private = (struct Private *) public; 
    // ... 
} 
int someProtectedFunction(struct Protected * protected) { 
    struct Private private = (struct Private *) protected; 
    // ... 
} 

Poi è solo una questione di fare in modo che dev/include non viene passato in giro.

2

Per i campi dati, è sufficiente non utilizzarli. Puoi fare alcuni trucchi come dare loro nomi pazzi per scoraggiare il loro uso, ma questo non fermerà le persone. L'unico vero modo per farlo è creare un'altra struttura privata a cui un puntatore void accede tramite le funzioni della libreria.

Per funzioni private - utilizzare le funzioni file static. Metti tutte le funzioni della tua libreria in un file C e dichiara quelle che vuoi essere private come static e non metterle in alcun file di intestazione.

1

Spesso per convenzione un membro privato ha un carattere di sottolineatura extra nel nome o qualcosa come _pri aggiunto. O forse un commento. Questa tecnica non esegue il controllo applicato dal compilatore per assicurarsi che nessuno acceda in modo inappropriato a quei campi, ma serve da avvertimento a chiunque legga una dichiarazione struct che i contenuti siano dettagli di implementazione e che non debbano dare loro un'occhiata.

Un'altra tecnica comune è esporre la struttura come un tipo incompleto. Ad esempio, nel file di intestazione, si potrebbe avere:

struct my_struct; 

void some_function(struct my_struct *); 

E nella realizzazione, o qualche colpo di testa interna che non è accessibile per i consumatori della biblioteca, si ha:

struct my_struct 
{ 
    /* Members of that struct */ 
}; 

Puoi anche fare trucchi simili con puntatori void, che ottengono il cast nel punto giusto nella porzione "privata" del codice. Questo approccio perde una certa flessibilità (ad esempio, non è possibile avere un'istanza allocata nello stack di un tipo non definito), ma ciò potrebbe essere accettabile.

Se si desidera un insieme di membri pubblici e privati, è possibile fare la stessa cosa di sopra, ma memorizzare il puntatore di struct privato come membro di quello pubblico e lasciarlo incompleto nei consumatori pubblici della libreria .

Anche se questo introduce qualche riferimento indiretto, che può compromettere le prestazioni. Ci sono alcuni (in genere non portabile, ma lavorerà su compilatori ragionevoli) TYPE-gioco di parole trucchi che si possono usare anche:

struct public_struct 
{ 
    int public_member; 
    int public_member2; 
    /* etc.. */ 
}; 

struct private_struct 
{ 
    struct public_struct base_members; 

    int private_member1; 
    int private_member2; 
}; 

void some_function(struct public_struct *obj) 
{ 
    /* Hack alert! */ 
    struct private_struct *private = (struct private_struct*)obj; 
} 

Questo presuppone anche che non è possibile memorizzare questi oggetti nello stack o in custodia statica , o ottenere le dimensioni al momento della compilazione.

Problemi correlati