2009-02-11 4 views
23

C'è un modo per avere una sorta di costruttore predefinito (come uno C++) per i tipi di utente C definiti con una struttura?Costruttore predefinito in C

Ho già una macro che funziona come un inizializzatore veloce (come quello per pthread_mutex) ma volevo sapere se è possibile per caso avere alcuni (o tutti) campi di una struttura compilata in dichiarazione.

Per esempio, con l'esempio pthread_mutex, vorrei

pthread_mutex_t my_mutex; 

per avere lo stesso effetto di

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; 

risposta

32

È possibile creare le funzioni di inizializzazione che richiedono un puntatore a una struttura. Questa era una pratica comune.

Anche funzioni che creano una struttura e inizializzano (come una fabbrica) - quindi non c'è mai un momento in cui la struct è "non inizializzata" nel codice "client". Naturalmente - che assume persone seguono la convenzione e utilizzare il "costruttore"/fabbrica ...

pseudo codice orribile senza errori il controllo su malloc o libero

somestruct* somestruct_factory(/* per haps some initializer agrs? */) 
{ 
    malloc some stuff 
    fill in some stuff 
    return pointer to malloced stuff 
} 


void somestruct_destructor(somestruct*) 
{ 
    do cleanup stuff and also free pointer 
    free(somestruct); 
} 

Qualcuno probabilmente venire avanti e spiegare come alcuni primi preprocessori/compilatori C++ lavoravano per fare tutto questo in C.

+3

Oh cfront, come ci manchi. – joeforker

+5

... come un'orribile eruzione cutanea. – Randolpho

+0

E in che modo Cfront gestisce il costruttore all'inizio? – claf

8

No, non direttamente. Il più vicino che potresti fare è scrivere una funzione che ha assegnato un'istanza e popolato alcuni campi.

4

è possibile scrivere una funzione che restituisce lo struct C:

struct file create_file(int i, float f) { 
    struct file obj = { i, f }; 
    // other code here... 
    return obj; 
} 

Se chiedersi se si può avere "normali" funzioni membro in C. Bene, si può in una certa misura. Preferisco lo stile object-as-first-argument. Passi un puntatore alla tua struct come primo argomento. In questo modo, è possibile avere diverse funzioni, che definisce l'interfaccia per gli oggetti:

int file_get_integer(struct file *self) { return self->i; } 
float file_get_float(struct file *self) { return self->f; } 

Se si scrive in quello stile, quello che hai alla fine è un tipo di dato astratto. Ho ragazzi che emulano la sintassi di chiamata di funzione membro utilizzato in C++ da avere puntatori a funzione nella loro struct e poi fare visto:

obj.get_integer(&obj); 

E 'utilizzato dal kernel linux per definire l'interfaccia di file driver di sistema. È uno stile di scrittura che può piacere o non piacere. Non mi piace troppo, perché continuo a utilizzare i membri delle strutture per i dati e non per emulare le funzioni dei membri chiamate ad apparire come nei linguaggi popolari orientati agli oggetti.

+0

Si spera che il compilatore sia abbastanza intelligente da evitare di copiare l'intera struttura nella dichiarazione di reso. – joeforker

+2

Come sottolinea indirettamente joeforker, è preferibile utilizzare un puntatore anziché un'istruzione di reso generale. Cosa succede se la tua struttura è particolarmente grande? La maggior parte dei compilatori copierà il valore restituito. – Randolpho

+0

se è molto grande lo farei prendere un puntatore. ma in questo caso (e probabilmente quasi tutti gli altri casi) è piuttosto piccolo. metterei una grande quantità di dati sull'heap e inserirò comunque un puntatore nella struct. –

0

No, le strutture non sono altro che un mucchio di dati. Non è possibile dichiarare funzioni all'interno delle strutture, quindi non c'è modo di creare un costruttore per esso.

+0

È possibile includere le funzioni nelle strutture in C o almeno i puntatori di funzione. Puoi mettere dei costruttori al loro interno e chiamarli per convenzione. L'ultima volta che ho visto qualcuno farlo è stato un vero casino e non mi è valsa la pena, ma puoi farlo. –

+0

-2 sembra duro per qualcosa che è perfettamente corretto. +1 per la giustizia. Puoi inserire un puntatore di funzione chiamato "costruttore", ma dovresti comunque dirlo per puntare a una funzione globale prima di chiamarla, quindi potresti anche chiamare direttamente la funzione globale! Inoltre, non otterresti "questo". – MrZebra

+0

+1 anche da me .. mentre è possibile essere creativi con i puntatori di funzione, c'è ben poca ragione per farlo. –

1

Non proprio. Se ricordo bene, le più vicine alle classi sono strutture. Alloca la memoria e popola i campi. Non vedo come si dovrebbe fare un generico tipo di costruttore per questo. In teoria, potresti scrivere una macro che farebbe parte di questo. Non sono sicuro che sia davvero utile.

+0

Puoi sviluppare la tua idea macro? – claf

+0

mai sentito parlare di funzioni di fabbrica? una semplice funzione che alloca e popola la struttura. nessuna necessità di macro. in effetti, questo è esattamente ciò che fanno i costruttori C++. – Javier

+0

@javier: sì, ma solo in un contesto Java. Non ho usato C dal 1994 e non desidero tornare :) – paul

2

Supponendo che si vuole fare questo in C, quindi la tua domanda non è di struct in C++:

Cosa faccio di solito è che ho appena creare una funzione, init_whatever, che prende un puntatore alla struttura (o altra variabile), e lo imposta sui valori che voglio.

Se si tratta di una variabile globale (che è inizializzato a zero) si potrebbe sorta di simulare un costruttore di argomenti meno da avere un flag "inizializzato" in esso, e poi lasciate che i vostri altre funzioni controllare che la bandiera, e inizializza la struct se il flag è zero. Non sono per niente sicuro che sia una buona idea, comunque.

E, come qualcun altro ha notato, si potrebbe anche fare qualcosa di orribile con le macro ...

3

In sostanza, C++ crea liste di puntatori che contengono gli indirizzi dei metodi. Questo elenco è chiamato definizione di classe (ci sono altri dati nella classe def, ma per ora ignoriamo questo).

Un modello comune per avere "classi" in C pura è definire una "classe struct". Uno dei campi della struct è una funzione factory che restituisce "istanze" della classe. Suggerisco di usare una macro per nascondere i calchi:

typedef struct __class * class; 
typedef void (*ctor_ptr)(class); 
struct class { 
    char * name; 
    ctor_ptr ctor; 
    ... destructor and other stuff ... 
} 

#define NEW(clz) ((struct something *)(((struct class *)clz)->ctor(clz))) 

Ora, è possibile definire le classi che avete con la creazione di strutture di tipo "class struct" per ogni classe che si ha e quindi chiamare il costruttore memorizzate in essi. Lo stesso vale per il distruttore, ecc

Se si desidera che i metodi per le istanze, li deve mettere nelle strutture di classe e di mantenere un puntatore alla classe nell'istanza struct:

#define NEW_SOMETHING() ((struct something *)NEW(&something_definition)) 
#define METHOD(inst, arg) ((struct something_class *)(((struct something *)inst)->clz)->method(inst, arg)) 

NEW_SOMETHING creerà una nuova istanza di "qualcosa" che utilizza la definizione di classe memorizzata nella struttura something_definition. METHOD invocherà un "metodo" su questa istanza. Si noti che per il codice reale, si vorrà verificare che inst sia in realtà un'istanza di something (confrontare i puntatori di classe, qualcosa del genere).

Per avere un'eredità è un po 'complicato e lasciato come esercizio per il lettore.

Nota che puoi fare tutto ciò che puoi fare in C++ in puro C. Un compilatore C++ non sostituisce magicamente la tua CPU con qualcos'altro. Ci vuole solo molto più codice per farlo.

Se si desidera esaminare un esempio, consultare glib (parte del progetto Gtk +).

+0

Mai sentito parlare di GOBJECT ? – joeforker

+0

@joe: hai mai sentito parlare dell'Amiga? :) –

+0

errore mio, la mia domanda riguardava il costruttore di default, mi piacerebbe avere un campo di una struct inizializzato alla dichiarazione senza dover chiamare un costruttore. – claf

16

C++ è diverso da C in questo caso nel senso che non ha "classi". Tuttavia, C (come molti altri linguaggi) può ancora essere utilizzato per la programmazione orientata agli oggetti. In questo caso, il tuo costruttore può essere una funzione che inizializza una struttura. Questo è lo stesso dei costruttori (solo una sintassi diversa). Un'altra differenza è che devi allocare l'oggetto usando malloc() (o qualche variante). In C++ si usa semplicemente l'operatore 'nuovo'.

ad es.codice C++:

class A { 
    public: 
    A() { a = 0; } 
    int a; 
}; 

int main() 
{ 
    A b; 
    A *c = new A; 
    return 0; 
} 

codice C equivalente:

struct A { 
    int a; 
}; 

void init_A_types(struct A* t) 
{ 
    t->a = 0; 
} 

int main() 
{ 
    struct A b; 
    struct A *c = malloc(sizeof(struct A)); 
    init_A_types(&b); 
    init_A_types(c); 
    return 0; 
} 

la funzione 'init_A_types' funziona da un costruttore sarebbe in C++.

1

Si potrebbe voler dare un'occhiata a un compilatore C++. Ti offre più funzioni orientate agli oggetti di quante tu possa ricordare in una lingua con una sintassi simile a C in un modo più elegante e standard rispetto al provare a costruire i costruttori e così via su C con librerie e macro.

Se ciò non funziona per voi, date un'occhiata a GObject. http://en.wikipedia.org/wiki/GObject. Si tratta di un sistema a oggetti per C usato in GTK e Gnome che praticamente manovelle "oggetti in C" a 11.

Da Wikipedia:

Il Sistema oggetto GLib, o GObject, è una libreria software libero (coperto dalla LGPL) che fornisce un sistema di oggetti portatili e un'interoperabilità trasparente tra le lingue.

Il sistema supporta costruttori, distruttori, ereditarietà singola, interfacce, metodi virtuali pubblici e privati ​​e così via. È anche molto più noioso e difficile rispetto a fare le stesse cose in C++. Divertiti!

10

Parliamo della soluzione ingegneristica completa che è stata considerata la migliore pratica in passato.

Il problema con le strutture è che tutto è pubblico, quindi non ci sono dati nascosti.

Possiamo aggiustarlo.

Si creano due file di intestazione. Uno è il file di intestazione "pubblico" utilizzato dai client del tuo codice. Esso contiene le definizioni in questo modo:

typedef struct t_ProcessStruct *t_ProcessHandle; 

extern t_ProcessHandle NewProcess(); 
extern void DisposeProcess(t_ProcessHandle handle); 

typedef struct t_PermissionsStruct *t_PermissionsHandle; 

extern t_PermissionsHandle NewPermissions(); 
extern void DisposePermissions(t_PermissionsHandle handle); 

extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm); 

quindi si crea un file di intestazione privato che contiene le definizioni in questo modo:

typedef void (*fDisposeFunction)(void *memoryBlock); 

typedef struct { 
    fDisposeFunction _dispose; 
} t_DisposableStruct; 

typedef struct { 
    t_DisposableStruct_disposer; /* must be first */ 
    PID _pid; 
    /* etc */ 
} t_ProcessStruct; 

typedef struct { 
    t_DisposableStruct_disposer; /* must be first */ 
    PERM_FLAGS _flags; 
    /* etc */ 
} t_PermissionsStruct; 

e poi nell'implementazione si può fare qualcosa di simile:

static void DisposeMallocBlock(void *process) { if (process) free(process); } 

static void *NewMallocedDisposer(size_t size) 
{ 
    assert(size > sizeof(t_DisposableStruct); 
    t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size); 
    if (disp) { 
     disp->_dispose = DisposeMallocBlock; 
    } 
    return disp; 
} 

static void DisposeUsingDisposer(t_DisposableStruct *ds) 
{ 
    assert(ds); 
    ds->_dispose(ds); 
} 

t_ProcessHandle NewProcess() 
{ 
    t_ProcessHandle proc = (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct)); 
    if (proc) { 
     proc->PID = NextPID(); /* etc */ 
    } 
    return proc; 
} 

void DisposeProcess(t_ProcessHandle proc) 
{ 
    DisposeUsingDisposer(&(proc->_disposer)); 
} 

Quello che succede è che tu fai dichiarazioni in avanti per le tue strutture nei tuoi file di intestazione pubblici. Ora le tue strutture sono opache, il che significa che i clienti non possono dick con loro. Quindi, nella dichiarazione completa, includi un distruttore all'inizio di ogni struttura che puoi chiamare genericamente. È possibile utilizzare lo stesso allocatore malloc per tutti la stessa funzione di smaltimento e così via. Rendi pubbliche le funzioni set/get per gli elementi che desideri vengano esposti.

Improvvisamente, il tuo codice è molto più sano. È possibile ottenere solo le strutture dagli allocatori o la funzione che chiama allocatori, il che significa che è possibile bloccare l'inizializzazione. Costruisci in distruttori in modo che l'oggetto possa essere distrutto. E su te vai. A proposito, un nome migliore di t_DisposableStruct potrebbe essere t_vTableStruct, perché è quello che è. Ora puoi costruire l'ereditarietà virtuale avendo un vTableStruct che è tutti i puntatori di funzione. Puoi anche fare cose che non puoi fare in un linguaggio puro (in genere), come cambiare elementi selezionati del vtable al volo.

Il punto importante è che lo è un modello di progettazione per rendere le strutture sicure e inizializzabili.

+0

È ironico come uno dei "vantaggi" di C++ sia quello di fornire l'incapsulamento dei dati attraverso specificatori protetti/privati, e tuttavia la maggior parte dei suoi schemi di utilizzo coinvolge classi che mostrano il loro coraggio a chiunque altro (ma non permettendo chiunque a toccarli, ovviamente). Uno potrebbe seguire lo stesso approccio di C, ma questo è il più delle volte scoraggiato perché è "non OOP", o si basa sull'approccio Pimpl, che nella mia esperienza introduce solo il codice di overhead e di codice. – Tomis

2

Ecco un po 'di magia macro intorno malloc(), memcpy() e composti C99 letterali:

#include <stdlib.h> 
#include <string.h> 
#include <stdio.h> 

#define new(TYPE, ...) memdup(&(TYPE){ __VA_ARGS__ }, sizeof(TYPE)) 

void * memdup(const void * obj, size_t size) 
{ 
    void * copy = malloc(size); 
    return copy ? memcpy(copy, obj, size) : NULL; 
} 

struct point 
{ 
    int x; 
    int y; 
}; 

int main() 
{ 
    int * i = new(int, 1); 
    struct point * p = new(struct point, 2, 3); 

    printf("%i %i %i", *i, p->x, p->y); 

    return 0; 
} 
2

Utilizzando le funzioni per creare e disporre di strutture è già stato menzionato. Ma in un commento hai detto che volevi un "costruttore predefinito" - penso che intendi che vuoi inizializzare alcuni (tutti?) Dei campi struct con i valori predefiniti.

Questo viene fatto in C utilizzando alcune convenzioni di codifica: funzioni, macro o un mix. Io di solito faccio qualcosa di simile a quanto segue -

struct some_struct { 
    int a; 
    float b; 
}; 
#define some_struct_DEFAULT { 0, 0.0f} 
struct some_struct *some_struct_create(void) { 
    struct some_struct *ptr = malloc(sizeof some_struct); 
    if(!ptr) 
     return ptr; 

    *ptr = some_struct_DEFAULT; 
    return ptr; 
} 
// (...) 
struct some_struct on_stack = some_struct_DEFAULT; 
struct some_struct *on_heap = some_struct_create(); 
1

Il fermo è la semplice programma che fa uso di un costruttore. La funzione "default_constructor" viene chiamata all'interno principale senza alcuna chiamata di funzione esplicita.

#include  <stdio.h> 

void __attribute__ ((constructor)) default_constructor() 
{ 
    printf("%s\n", __FUNCTION__); 
} 

int main() 
{ 
    printf("%s\n",__FUNCTION__); 
    return 0; 
} 

uscita: default_constructor principale

All'interno della funzione di costruzione è possibile includere alcune dichiarazioni di inizializzazione nella funzione di costruzione e, infine, può fare liberando nel distruttore, come daini,

#include <stdio.h> 
#include <stdlib.h> 

struct somestruct 
{ 
    int  empid; 
    char * name; 
}; 

struct somestruct * str1; 

void __attribute__ ((constructor)) a_constructor() 
{ 
    str1 = (struct somestruct *) malloc (sizeof(struct somestruct)); 
    str1 -> empid = 30228; 
    str1 -> name = "Nandan"; 
} 

void __attribute__ ((destructor)) a_destructor() 
{ 
    free(str1); 
} 

int main() 
{ 
    printf("ID = %d\nName = %s\n", str1 -> empid, str1 -> name); 
    return 0; 
} 

Speranza questo ti aiuterà.