Devo inizializzare le strutture C tramite parametro o per valore di ritorno? [chiuso]

33

La società a cui lavoro sta inizializzando tutte le loro strutture dati attraverso una funzione di inizializzazione in questo modo:

//the structure
typedef struct{
  int a,b,c;  
} Foo;

//the initialize function
InitializeFoo(Foo* const foo){
   foo->a = x; //derived here based on other data
   foo->b = y; //derived here based on other data
   foo->c = z; //derived here based on other data
}

//initializing the structure  
Foo foo;
InitializeFoo(&foo);

Ho ricevuto qualche tentativo di inizializzare le mie strutture in questo modo:

//the structure
typedef struct{
  int a,b,c;  
} Foo;

//the initialize function
Foo ConstructFoo(int a, int b, int c){
   Foo foo;
   foo.a = a; //part of parameter input (inputs derived outside of function)
   foo.b = b; //part of parameter input (inputs derived outside of function)
   foo.c = c; //part of parameter input (inputs derived outside of function)
   return foo;
}

//initialize (or construct) the structure
Foo foo = ConstructFoo(x,y,z);

C'è un vantaggio rispetto all'altro?
Quale dovrei fare e come potrei giustificarlo come una pratica migliore?

    
posta Trevor Hickey 20.07.2015 - 17:41
fonte

5 risposte

24

Nel secondo approccio non avrai mai un Foo semi-inizializzato. Mettere tutta la costruzione in un posto sembra un posto più sensato e ovvio.

Ma ... il 1o modo non è poi così male, ed è spesso usato in molte aree (c'è anche una discussione sul modo migliore per iniettare le dipendenze, o l'iniezione di proprietà come la prima, o l'iniezione del costruttore come il 2 °). Né è sbagliato.

Quindi se nessuno dei due è sbagliato e il resto della compagnia usa l'approccio n. 1, allora dovresti adattarti alla base di codice esistente e non provare a rovinarla introducendo un nuovo modello. Questo è davvero il fattore più importante in gioco qui, gioca bene con i tuoi nuovi amici e non cercare di essere quel fiocco di neve speciale che fa le cose in modo diverso.

    
risposta data 20.07.2015 - 17:49
fonte
21

Entrambi gli approcci raggruppano il codice di inizializzazione in una singola chiamata di funzione. Fin qui, tutto bene.

Tuttavia, ci sono due problemi con il secondo approccio:

  1. Il secondo non costruisce l'oggetto risultante, inizializza un altro oggetto sullo stack, che viene quindi copiato sull'oggetto finale. Questo è il motivo per cui vedrei il secondo approccio come leggermente inferiore. Il push-back che hai ricevuto è probabilmente dovuto a questa copia estranea.

    Questo è ancora peggio quando si ricava una classe Derived da Foo (le strutture sono ampiamente utilizzate per l'orientamento degli oggetti in C): con il secondo approccio, la funzione ConstructDerived() chiamerebbe ConstructFoo() , copia la risultante oggetto Foo temporaneo nello slot della superclasse di un oggetto Derived ; terminare l'inizializzazione dell'oggetto Derived ; solo per avere l'oggetto risultante copiato di nuovo al ritorno. Aggiungi un terzo livello e l'intera cosa diventa completamente ridicola.

  2. Con il secondo approccio, le funzioni ConstructClass() non hanno accesso all'indirizzo in costruzione dell'oggetto. Ciò rende impossibile collegare gli oggetti durante la costruzione, poiché è necessario quando un oggetto deve registrarsi con un altro oggetto per un callback.

Infine, non tutte le structs sono classi complete. Alcuni structs raggruppano efficacemente un gruppo di variabili, senza alcuna restrizione interna ai valori di queste variabili. typedef struct Point { int x, y; } Point; sarebbe un buon esempio di questo. Per questi l'uso di una funzione di inizializzazione sembra eccessivo. In questi casi, la sintassi letterale composta può essere comoda (è C99):

Point = { .x = 7, .y = 9 };

o

Point foo(...) {
    //other stuff

    return (Point){ .x = n, .y = n*n };
}
    
risposta data 20.07.2015 - 20:17
fonte
1

A seconda del contenuto della struttura e del particolare compilatore in uso, entrambi gli approcci potrebbero essere più veloci. Un modello tipico è che le strutture che soddisfano determinati criteri possono essere restituite nei registri; per le funzioni che restituiscono altri tipi di struttura, il chiamante è obbligato ad allocare da qualche parte lo spazio per la struttura temporanea (tipicamente sullo stack) e passare il suo indirizzo come parametro "nascosto"; nei casi in cui il ritorno di una funzione è memorizzato direttamente su una variabile locale il cui indirizzo non è contenuto da alcun codice esterno, alcuni compilatori potrebbero essere in grado di passare direttamente l'indirizzo di quella variabile.

Se un tipo di struttura soddisfa i requisiti di un'implementazione particolare da restituire nei registri (ad es. essendo non più grandi di una parola macchina o riempendo esattamente due parole macchina) con una funzione restituire la struttura potrebbe essere più veloce di passare l'indirizzo di una la struttura, soprattutto perché l'esposizione dell'indirizzo di una variabile al codice esterno che potrebbe conservare una copia di esso potrebbe precludere alcune ottimizzazioni utili. Se un tipo non soddisfa tali requisiti, il codice generato per una funzione che restituisce una struttura sarà simile a quello di una funzione che accetta un puntatore di destinazione; il codice chiamante sarebbe probabilmente più veloce per il modulo che accetta un puntatore, ma tale modulo perde alcune opportunità di ottimizzazione.

Peccato che C non fornisca un mezzo per dire che una funzione è proibita dal tenere una copia di un puntatore passato (semantica simile a un riferimento C ++) poiché passare un puntatore così limitato potrebbe ottenere la prestazione diretta i vantaggi di passare un puntatore a un oggetto preesistente, ma allo stesso tempo evitare i costi semantici di richiedere a un compilatore di considerare l'indirizzo di una variabile "esposto".

    
risposta data 20.07.2015 - 22:01
fonte
1

Un argomento a favore dello stile "output-parameter" è che consente alla funzione di restituire un codice di errore.

struct MyStruct {
    int x;
    char *y;
    // ...
};

int MyStruct_init(struct MyStruct *out) {
    // ...
    char *c = malloc(n);
    if (!c) {
        return -1;
    }
    out->y = c;
    return 0;  // Success!
}

Considerando alcune serie di strutture correlate, se l'inizializzazione può fallire per qualcuno di essi, può valere la pena che tutti usino lo stile dei parametri esterni per coerenza.

    
risposta data 21.07.2015 - 01:59
fonte
0

Suppongo che il tuo focus sia sull'inizializzazione tramite il parametro di output rispetto all'inizializzazione tramite return, non sulla discrepanza nel modo in cui vengono forniti gli argomenti di costruzione.

Si noti che il primo approccio potrebbe consentire a Foo di essere opaco (sebbene non con il modo in cui lo si utilizza attualmente), e che di solito è auspicabile per la manutenibilità a lungo termine. Si potrebbe prendere in considerazione, ad esempio, una funzione che alloca e una struttura opaca Foo senza inizializzarla. O forse hai bisogno di reinizializzare una struttura Foo che è stata precedentemente inizializzata con valori diversi.

    
risposta data 21.07.2015 - 13:35
fonte

Leggi altre domande sui tag