È una cattiva pratica leggere un file di grandi dimensioni nel costruttore?

7

Quindi, sto cercando di creare un'implementazione della struttura dati in lingua inglese in C ++. Ho creato una classe Trie e TrieNode . La classe TrieNode prende nel suo costruttore un vector<string> che è una lista di parole per costruire il Trie.

La mia soluzione per ottenere questo vector<string> era di usare una classe EnglishWordsListGenerator , che nel suo costruttore, contiene una stringa del nome file di un file che presumibilmente contiene un elenco di parole inglesi valide. (Il sempre popolare file enable1.txt ).

Sono nuovo di C ++, quindi non so se è considerato una buona pratica o non leggere il file direttamente dal costruttore durante l'inizializzazione dell'oggetto. Dopo tutto, cosa succede se l'operazione fallisce? D'altra parte, però, so che Bjarne spinge pesantemente per RAII. Qual è la cosa giusta da fare qui?

    
posta Bassinator 09.03.2017 - 18:16
fonte

4 risposte

15

Quando vedi una classe con una firma del costruttore come

 EnglishWordsListGenerator(const std::string &wordFileName)

Penso che sia abbastanza ovvio che questo costruttore leggerà il file dato (e quindi avrà bisogno di un po 'di tempo), e non dovrebbe essere difficile capire che il chiamante deve preoccuparsi di possibili eccezioni da questo (perché il file IO può fallire). Quindi, nonostante le altre risposte che dicono, questo design è ok .

Non fraintendetemi, in futuro potreste avere dei requisiti in cui questo design potrebbe non essere più sufficiente, ma fintanto che questa semplice interfaccia e comportamento è tutto ciò di cui il vostro programma ha bisogno, vorrei attenermi ai KISS e YAGNI principi ed evitare di complicare eccessivamente le cose fornendo un metodo di inizializzazione separato "just in case".

    
risposta data 09.03.2017 - 22:32
fonte
7

Se sei nuovo in C ++, potrebbe essere desiderabile evitare di fare grandi lavori in un costruttore. I costruttori sono strettamente legati al modo in cui il linguaggio si comporta e davvero non vuoi che il costruttore venga chiamato inaspettatamente ( explicit è tuo amico!). I costruttori sono anche piuttosto unici nel senso che non possono restituire un valore. Ciò può rendere più complicata la gestione degli errori.

Questo tipo di raccomandazione diventa meno importante man mano che si impara di più su C ++ e su tutte le diverse cose che possono causare il richiamo dei costruttori. Nel mondo perfetto, la risposta corretta è che la tua biblioteca dovrebbe fare esattamente ciò che l'utente voleva in ogni momento. Molto spesso in C ++ è meglio implementato facendo cose all'interno dei costruttori. Tuttavia, vuoi solo assicurarti di aver capito abbastanza i costruttori da non intrappolare gli utenti in un angolo. Debugare perché il programma funziona male perché un costruttore è stato richiamato quando un utente non ha intenzione di invocarlo può essere un problema.

Allo stesso modo, la gestione delle eccezioni nei costruttori può essere più difficile perché l'ambito in cui si verifica l'eccezione è intrecciato con l'ambito in cui viene creata una variabile, piuttosto che dove si desidera utilizzarlo. Le eccezioni possono essere esasperanti quando si verificano prima che % venga eseguitomain(), negandoti la possibilità di gestire tale eccezione per l'utente. Questo può accadere se usi variabili globali e gli errori possono essere criptici.

Un'opzione che si trova tra gli estremi è usare i metodi di fabbrica. Se i tuoi utenti si aspettano di utilizzare i metodi di fabbrica, ha senso che potrebbero fare un po 'di inizializzazione in più a quel punto. Un metodo factory non verrà chiamato per caso come potrebbe fare un costruttore, quindi è più difficile rovinarlo.

Una volta che sei sicuro di capire le conseguenze del lavoro svolto in un costruttore, può essere uno strumento potente. L'ultimo esempio di lavoro in un costruttore potrebbe essere la classe lock_guard . Le guardie di blocco sono costruite passando loro un blocco come un mutex. Chiamano .lock() su di esso durante il loro costruttore (ovvero facendo un vero lavoro) e .unlock() su di esso durante il loro distruttore.

Queste classi fanno finta di fare del lavoro reale nel costruttore perché sono progettate per essere utilizzate in questo modo. Chiunque lavori con un lock_guard è ben consapevole del fatto che il costruttore funziona perché il codice che lo utilizza assomiglia a qualcosa di simile:

{
    lock_guard guardMe(myLock); // locks myLock
    doSomeStuff();
    doMoreStuff();
    // as I leave this block, guardMe will unlock myLock.  It will do so
    // even if I leave via an exception!
}

Se osservi le implementazioni delle classi lock_guard , troverai facilmente l'80% della loro attenzione o più si concentra su come gestire correttamente i costruttori per evitare sorprese. Implementati correttamente, sono molto potenti. Implementato in modo improprio lock_guards può essere la rovina della tua esistenza perché può sembrare che facciano una cosa quando ne fa un'altra.

    
risposta data 09.03.2017 - 21:43
fonte
5

Non è una buona pratica caricare un oggetto da un file nel costruttore.

Al di là dei buoni argomenti del C ++ nelle altre risposte, aggiungerei un principio di codice pulito: una funzione dovrebbe fare solo una cosa . Ok, il costruttore non è una funzione normale, ma il principio si applica ancora.

Suggerisco quanto segue:

  • mantieni il Trie il più generale possibile per mirare al riutilizzo. Ci sono molte altre applicazioni che potrebbero usarlo.
  • tenerlo indipendente dal modo in cui lo alimenta: idealmente. Diverse applicazioni potrebbero utilizzare approcci diversi, ad esempio per alimentarlo da string o da istream . Dopotutto, le versioni future della tua app potevano inviare le parole da un database con lingue diverse.
  • utilizza un modello di progettazione del builder , per assemblare i pezzi del processo di costruzione. Il builder quindi caricherà il trie da una sorgente, usando un qualche tipo di approccio generale (ad esempio, Crea il trie, apri il sorgente, esegui l'iterazione attraverso i dati di origine e inseriscilo nel trie, una volta fatto restituire il trie). Potresti quindi creare un builder concreto per il caricamento da un file e optare facilmente per altre alternative.
risposta data 09.03.2017 - 22:39
fonte
1

Non penso che leggerei un file nel costruttore per un paio di motivi:

  1. rallenterà in modo significativo la creazione di oggetti e potenzialmente il     avvio del tuo programma.
  2. come hai detto, ci sono opzioni limitate per la gestione degli errori. Dovresti o mettere l'intero programma in una prova ... catturare il blocco o impostare una sorta di bandiera se qualcosa     va storto
  3. E se hai bisogno di leggerlo più di una volta? tu     dovrebbe avere un duplicato del codice o dividere il codice in     una funzione chiamata dal costruttore, nel qual caso basta farlo dopo     l'oggetto è comunque creato.

Sono sicuro che ci sono altre ragioni, quelle sono solo le prime che mi sono venute in mente. Se è un file piccolo e lo stai leggendo solo una volta, probabilmente stai bene.

    
risposta data 09.03.2017 - 21:17
fonte

Leggi altre domande sui tag