L'omissione di "destructors" in C sta portando YAGNI troppo lontano?

9

Sto lavorando a un'applicazione media embedded in C usando tecniche simili a OO. Le mie "classi" sono moduli .h / .c che utilizzano strutture di dati e strutture di puntatori di funzioni per emulare l'incapsulamento, il polimorfismo e l'iniezione di dipendenza.

Ora, ci si aspetterebbe che una funzione myModule_create(void) abbia una controparte myModule_destroy(pointer) . Ma il progetto è incorporato, le risorse che sono istanziate realisticamente non dovrebbero mai essere rilasciate.

Voglio dire, se ho 4 porte seriali UART e creo 4 istanze UART con i pin e le impostazioni richieste, non c'è assolutamente alcun motivo per voler distruggere UART # 2 ad un certo punto durante il runtime.

Quindi, seguendo il principio YAGNI (non ne avrò bisogno), dovrei omettere i distruttori? Mi sembra molto strano, ma non riesco a pensare a un uso per loro; le risorse vengono liberate quando il dispositivo si spegne.

    
posta Asics 22.07.2014 - 20:17
fonte

4 risposte

11

If you don't provide a way to dispose the object, you are passing a clear message that they have "infinite" lifetime once created. If this makes sense to your application, I say: do it.

Glampert ha ragione ; qui non c'è bisogno di distruttori. Potrebbero solo creare un codice gonfiato e una trappola per gli utenti (usare un oggetto dopo che il suo distruttore viene chiamato è un comportamento non definito).

Tuttavia, dovresti essere sicuro che non c'è davvero bisogno di smaltire gli oggetti. Ad esempio, devi avere un oggetto per una UART che non è attualmente in uso?

    
risposta data 23.07.2014 - 02:44
fonte
3

Il modo più semplice che ho trovato per rilevare perdite di memoria è di poter uscire in modo pulito dall'applicazione. Molti compilatori / ambienti forniscono un modo per controllare la memoria che è ancora assegnata quando l'applicazione si chiude. Se non ne viene fornito uno di solito c'è un modo per aggiungere del codice subito prima di uscire, che può capirlo.

Quindi, fornirei sicuramente costruttori, distruttori e logica di spegnimento anche in un sistema embedded che "teoricamente" non dovrebbe mai uscire per il solo rilevamento delle perdite di memoria. In realtà, il rilevamento delle perdite di memoria è ancora più importante se il codice dell'applicazione non dovesse mai uscire.

    
risposta data 23.07.2014 - 15:55
fonte
3

Nel mio sviluppo, che fa ampio uso di tipi di dati opachi per promuovere un approccio di tipo OO, anch'io ho affrontato questa domanda. All'inizio, ero decisamente nel campo dell'eliminazione del distruttore dalla prospettiva YAGNI, così come nella prospettiva del "codice morto" MISRA. (Avevo un sacco di spazio per le risorse, non era una considerazione.)

Tuttavia ... la mancanza di un distruttore può rendere più difficili i test, come nei test automatici di integrazione / unità. Convenzionalmente, ogni test dovrebbe supportare un setup / teardown in modo che gli oggetti possano essere creati, manipolati e poi distrutti. Sono distrutti al fine di assicurare un punto di partenza pulito e non contaminato per il test successivo. Per fare ciò, la classe ha bisogno di un distruttore.

Pertanto, nella mia esperienza, il "non" in YAGNI risulta essere un "sono" e alla fine ho creato dei distruttori per ogni classe, sia che pensassi che ne avrei avuto bisogno o meno. Anche se ho saltato i test, almeno un distruttore progettato correttamente esiste per il povero slob che mi segue avrà uno. (E, a un valore molto inferiore, rende il codice più riutilizzabile in quanto può essere utilizzato in un ambiente in cui sarebbe distrutto.)

Sebbene si indirizzi a YAGNI, non indirizza il codice morto. Per questo, trovo che una macro di compilazione condizionale come #define BUILD_FOR_TESTING consente di eliminare il distruttore dal build finale di produzione.

Operando in questo modo: hai un distruttore per test / riutilizzo futuro e soddisfi gli obiettivi di progettazione di YAGNI e le regole di "nessun codice morto".

    
risposta data 04.08.2014 - 22:21
fonte
0

Potresti avere un distruttore no-op, qualcosa come

  void noop_destructor(void*) {};

quindi imposta il distruttore di Uart usando forse

  #define Uart_destructor noop_destructor

(aggiungi il cast adatto se necessario)

Non dimenticare di documentare. Forse vuoi anche

 #define Uart_destructor abort

In alternativa, caso speciale nel codice comune che chiama distruttore il caso in cui la funzione del puntatore del distruttore è NULL per evitare di chiamarla.

    
risposta data 23.07.2014 - 16:00
fonte

Leggi altre domande sui tag