Come creare "costruttori" per le strutture in C

6

Il problema

Ho questa struttura che voglio creare un "costruttore" per questo.

struct example {
    int x, y, z; /* various members */
    struct another *another; /* pointer to another structure */
}

I due diversi modi che conosco

  1. Uso di funzioni che creano ed eliminano strutture su heap

Posso creare strutture creando funzioni che le allocano sullo heap in questo modo:

/* create example */
struct example *example_new(int x, int y, int z) {
    struct example *p = malloc(sizeof *p);
    p->x = x;
    p->y = y;
    p->z = z;
    p->another = another_new();
}

/* delete example */
void example_new(struct example *p) {
    another_del(p->another);
    free(p);
}
  1. Utilizzo di funzioni che inizializzano e liberano la memoria per la struttura tramite il puntatore

Oppure potrei initalizzare la struttura passando il puntatore alla funzione e avere l'utente responsabile dell'allocazione e deallocazione della memoria per esso.

/* initalize example */
void example_init(struct example *p, int x, int y, int z) {
    assert(p);
    p->x = x;
    p->y = y;
    p->z = z;
    p->another = another_new();
}

/* free memory allocated for example */
void example_free(struct example *p) {
    another_del(p->another);
}

I miei pensieri

Di solito preferisco il primo approccio quando creo strutture ricorsive (come Alberi e elenchi collegati ) ma uso il secondo approccio in tutti gli altri casi.

Ho provato a utilizzare il secondo approccio per una struttura ricorsiva e si è rivelato piuttosto complicato.

La domanda

Come scegli tra questi due modi? C'è un terzo modo per risolvere questo problema?

    
posta wefwefa3 07.01.2016 - 19:20
fonte

3 risposte

4

Non c'è una risposta che sarà sempre quella giusta, perché diversi programmi avranno esigenze diverse. Se hai abbastanza informazioni per fare una chiamata informata in un modo o nell'altro, è così che dovresti andare. (Ad esempio, se è necessario che il tuo programma abbia il throughput più elevato possibile, forzare una coppia malloc() / free() ogni volta che alcune funzioni devono utilizzare la struttura probabilmente non volerà.)

Se intendi creare una libreria generica, non è molto più complicato svilupparla in un modo che supporti entrambi:

void example_init(struct example *p, int x, int y, int z) - Inizializza la struttura puntata da p . Questo può essere chiamato da chiunque abbia un struct example se si tratta di un automatico, allocato sull'heap o estratto da un pool.

void example_destroy(struct example *p) - Fa tutto il necessario per desinizializzare la struttura puntata da p . (Nel tuo caso, ciò libererebbe la memoria allocata per il membro another .) Nota che non libera p , perché non ha alcuna conoscenza se la struttura sia attiva o meno lo heap o se il chiamante non sta per riutilizzarlo chiamando di nuovo example_init() .

struct example * example_new(int x, int y, int z) - Alloca lo spazio per la struttura sull'heap, chiama example_init() contro di esso e restituisce il puntatore.

void example_del(struct example * p) - Chiama example_destroy(p) e poi libera p .

C'è un rischio per questo approccio, ovvero che qualcuno potrebbe provare a fare coppie di example_init() / example_del() o example_new() / example_destroy() chiamate. Strumenti come valgrind e alcune implementazioni di free() possono estirpare automaticamente questi problemi. Un altro approccio sarebbe quello di mettere da parte un po 'da qualche parte nella struttura che può essere impostata quando la struttura era nell'heap e usare asserzioni per lamentarsi se qualcuno lo fa nel modo sbagliato. ( example_del() dovrebbe cancellare il bit prima di chiamare example_destroy() .)

    
risposta data 07.01.2016 - 20:03
fonte
3

Il vantaggio principale della prima opzione è che incapsula l'allocazione di memoria nella "nuova" e "cancellazione". Devi solo ricordare di creare e distruggere le tue strutture. Con la seconda opzione, devi ricordare separatamente per creare / distruggere e allocare / liberare i tuoi oggetti.

Il vantaggio della seconda opzione è che ti permette di creare oggetti sullo stack:

struct example;
example_init(&example, 1,2,3);

Questo può avere grandi vantaggi prestazionali rispetto all'allocazione sull'heap. Tuttavia, devi fare molta attenzione a chiamare example_free prima che il tuo oggetto vada fuori ambito.

Quindi l'opzione 1 dovrebbe essere generalmente meno incline agli errori e dovrebbe essere preferibile a meno che i tuoi requisiti non richiedano i vantaggi in termini di prestazioni di mettere questi oggetti in pila.

    
risposta data 07.01.2016 - 19:38
fonte
1

La risposta a quale approccio utilizzare dipende dalla strategia di gestione della memoria che gli utenti della tua API impiegheranno. Ad esempio, utilizzando un pool di oggetti o La memoria basata sulla regione richiede l'utilizzo della strategia 2. Se c'è qualche possibilità che i consumatori della API desiderino un maggiore controllo sulla gestione della memoria, la strategia 2 è preferibile.

Se sei convinto che malloc e free siano sufficienti per tutti gli utenti della tua API, la strategia 1 è adeguata. Anche in questo caso sembra ragionevole fornire le funzioni di 2 e definire le versioni malloc e free in termini di quelle funzioni. Questo ha il vantaggio di separare i compiti logicamente distinti di allocare memoria e inizializzare quella memoria.

    
risposta data 07.01.2016 - 19:40
fonte

Leggi altre domande sui tag