Ha senso avere una funzione di init separata?

1

Sono stato creato un tipo opaco e ho due opzioni per la sua gestione.

Il primo appare così:

type_t *a = type_init();
int err;

err = type_do(a, "foo", "bar", FLAGBIT1|FLAGBIT2, NULL);
if (!err) {
    type_print_result(a);
    ...
}

type_free(a);

Il secondo è:

type_t *a;
int err;

err = type_do(&a, "foo", "bar", FLAGBIT1|FLAGBIT2, NULL);
if (!err) {
    type_print_result(a);
    ...
}

type_free(a);

type_do è la funzione che calcola i risultati su cui possiamo lavorare. Dato che potresti sottostimare nel secondo caso l'inizializzazione avviene entro type_do , ecco perché devo passare l'indirizzo di a . È assolutamente necessario chiamare type_do prima di fare qualsiasi altra cosa con type_t . C'è qualche ragione per avere una funzione init separata?

    
posta Nick 14.09.2014 - 06:34
fonte

5 risposte

5

Di norma, disegno l'API in modo che il minor numero possibile di cose possa andare storto. Ciò significa:

  • Solo una funzione di init in modo che non ci siano stati intermedi.
  • Deve essere sicuro chiamare la funzione di distruzione anche se la funzione init fallisce, cioè if (ptr == NULL) return; anche se nessuna altra funzione ha i controlli NULL o se NULL controlla le altre funzioni è fatale.
  • Assicurati che l'errore non possa essere ignorato, sia da warn_unused_result (con le impostazioni -Werror appropriate), sia che rendi l'errore un argomento (e restituisca il risultato).
risposta data 14.09.2014 - 07:12
fonte
2

Preferisco vedere questa situazione come una che richiede una fabbrica. La convenzione per una fabbrica è che restituisce sempre un oggetto valido, o forse solleva un'eccezione se davvero non può. Il codice quindi assomiglia a questo.

type_t a = type_factory("foo", "bar", FLAGBIT1|FLAGBIT2, NULL);
if (!a.valid) {
    type_print_result(a);
    ...
}
type_free(a);

o

if (!type_is_ok(a)) { ... }

Questo idioma funziona meglio nei linguaggi OO, ma è perfettamente utilizzabile anche qui.

@glampert fa il punto eccellente che una fabbrica garantisce un default sicuro nel caso ci sia un motivo per cui un nuovo oggetto non può essere creato. Semplifica anche il mock dell'oggetto a scopo di test.

    
risposta data 14.09.2014 - 13:55
fonte
1

Vedo due ragioni per cui un'indicazione iniziale separata ha un senso. Innanzitutto, vi è il principio "Single Level of Abstraction" , che aiuta a rendere il codice più leggibile e più evolutivo. Ciò significa che se type_init e type_free sono una coppia di funzioni associate, ha perfettamente senso chiamarle allo stesso livello quando possibile.

La seconda ragione è che nel caso in cui l'inizializzazione fallisca, potresti essere in grado di capirlo all'interno della funzione precedente, quindi il tuo codice dovrebbe probabilmente assomigliare a questo:

type_t *a = type_init();
int err;

if(!a) {
    // signal to the caller that initialization failed
    return;
}

err = type_do(a, "foo", "bar", FLAGBIT1|FLAGBIT2, NULL);
if (!err) {
    type_print_result(a);
        ...
}
else {
    // signal to the caller that type_do failed
}
type_free(a);

(tuttavia, type_free dovrebbe ancora comportarsi bene quando ottiene uno 0 superato, ovviamente).

    
risposta data 03.01.2015 - 10:32
fonte
0

Data la tua descrizione, l'unica ragione che posso vedere per una funzione di inizializzazione separata è quella di consentire la possibilità che, a volte in futuro, non sarà più assolutamente necessario chiamare type_do(type_t*, ...) prima di fare qualsiasi altra cosa con type_t .

In ogni caso, dovresti documentare che type_init() o type_do() restituisce un oggetto che il chiamante dovrebbe liberare chiamando type_free() quando non è più necessario.

    
risposta data 14.09.2014 - 18:33
fonte
0

Sì, ha senso avere una funzione init separata.

Il primo caso è grandioso: definisce una variabile e inizializza in 1 step. Paradigma facile da usare.

Nel secondo caso, se utilizzato, il codice dovrebbe essere tollerante di inizializzazione con 0 e / o NULL e accettabile per un type_free() successivo. Rational: ciò che gli utenti faranno in quanto vi è un'inizializzazione molto ampia con funzioni come memset(p,0,size) e calloc() , ecc. E contiamo su quella come codifica accettabile. Perché creare un type_free() che non regge bene con 0 ?

type_t *a;
err = type_do(&a, "foo", "bar", FLAGBIT1|FLAGBIT2, NULL);
type_free(a);
...
type_t *b = 0;
type_free(b);

Anche nel primo caso sosterrei anche un% tollerantetype_free(0);.

    
risposta data 03.01.2015 - 09:38
fonte

Leggi altre domande sui tag