Modo preferito di gestire gli errori durante il caricamento di un oggetto da un file

2

Se voglio caricare un oggetto da un file, ci sono un certo numero di cose che possono andare storte. Pertanto, quando si fa ciò si ha bisogno di un modo per gestire gli errori. In alcune lingue, come haskell, è possibile restituire un oggetto Maybe che potrebbe contenere l'oggetto appena creato. In C++ , sebbene sia in linea di principio possibile creare una classe di convenienza, di solito non è il modo più diretto per fare le cose. Di seguito elencho vari modi in cui gli errori potrebbero essere gestiti durante il caricamento di un oggetto da un file in C++ .

  • Restituisce un puntatore:

Object* load_from_file(const char* filename);

Restituendo un puntatore, possiamo restituire nullptr nel caso in cui si verifichi un problema durante il caricamento dell'oggetto.

  • Prendi un riferimento a un oggetto costruito di default e restituisci false se si verifica un problema:

bool load_from_file(const char* filename, Object& obj);

Utilizzando questo metodo, è necessario implementare un metodo di inizializzazione separato per l'oggetto, che verrà richiamato dopo averlo caricato dal file.

  • Genera un'eccezione nella funzione load_from_file :

Object load_from_file(const char* filename);

In generale, credo che il lancio di un'eccezione in questo caso potrebbe essere negativo, in quanto non si tratta di un caso veramente "eccezionale".

  • Carica dati su un intermedio struct :

struct ObjectData{
    //data
};
bool load_from_file(const char* filename, ObjectData& data);
//example use:
ObjectData data;
if(load_from_file("path_to_some_file", data)){
    Object(data);
    //etc...
}

La differenza qui con il secondo metodo è che non è necessario creare un metodo di inizializzazione per Object .

In generale, qual è il metodo preferito per il problema sopra descritto o quale metodo è preferito in quali casi?

    
posta Grieverheart 02.12.2014 - 16:05
fonte

2 risposte

3

Non confondere "eccezione" con "raro"

Il punto principale delle eccezioni e della gestione delle eccezioni è diverso tipi di codice a parte:

  • Il codice che fornisce la funzionalità principale (il codice "percorso felice")
  • Tutto il resto: il codice che gestisce tutti quei 973 altri casi in cui non tutto ha funzionato in modo meraviglioso e preciso per il tuo le funzionalità principali gradite.

Tirando fuori dal tuo codice il codice per le affermazioni non proprio felici codice di percorso felice rende il secondo piacevolmente semplice e comprensibile. Ecco di cosa tratta la gestione delle eccezioni, davvero (oltre a mantenere l'altra 973 casi diversi l'uno dall'altro; oltre a fare in modo che l'errore si verificherà la gestione degli errori verificatisi durante la gestione degli errori anche in modo affidabile)

Quindi la tua formulazione "ci sono un certo numero di cose che possono andare storte" è una buona indicazione che un'eccezione è probabilmente la più approccio manutentivo nel lungo periodo per il tuo caso. E questo rimane vero anche se le eccezioni sono più frequenti rispetto al caso del percorso felice.

    
risposta data 02.12.2014 - 16:22
fonte
3

Generally, what is the preferred method for the problem described above, or which method is preferred in which cases?

Il metodo preferito è simile a questo:

class Object { ... };

std::istream& operator>>(std::istream& in, Object& obj) { /* classic "in" operator*/ }

Codice cliente (1):

Object o;
if(std::cin >> o) {
    // do something with o
}

Codice cliente (2):

Object o;
std::cin.exceptions(std::ios::failbit);
std::cin >> o; // will throw on failure
// do something with o

Spetta all'implementazione dell'operatore di input, impostare il failbit per in (che può o non può generare un'eccezione), nel caso in cui i valori letti dallo stream siano validi per i tipi di dati, ma non valido per essere inserito nell'istanza Object .

Questo è preferito, perché:

  • è flessibile rispetto al tipo di flusso (con questo codice, puoi anche usare un'istanza std :: stringstream e serializzare i tuoi dati in una stringa)
  • non impone la strategia di gestione degli errori (il codice del client arriva a decidere se lanciare o meno un'eccezione)
  • minimizza la conoscenza cfr. Law of Demeter (la funzione di caricamento non ha bisogno di sapere cosa sia un file o un percorso di file)
  • si integra e si adatta correttamente con il supporto I / O C ++ integrato; ciò significa che puoi farlo facilmente:

    Object a;
    int b;
    std::string word;
    
    std::cin >> a >> b >> word; // a is read the same as any int or std::string
    

Svantaggi: - a meno che tu non stia attento a leggere, gli errori potrebbero passare (considera il mio primo esempio, senza if come esempio).

Modifica : un ulteriore vantaggio di scrivere codice I / O con operatori i / o-stream è che si ottiene l'integrazione gratuita con altro codice che utilizza std :: i / o-stream. Ad esempio, dopo aver definito il tuo operatore, potresti utilizzare boost :: lexical_cast con Object istanze:

#include <boost/lexical_cast.hpp>

std::ostringstream buffer;
Object a = { /* data here */ };
buffer << a;
auto b = boost::lexical_cast<Object>(buffer.str());
    
risposta data 02.12.2014 - 16:39
fonte

Leggi altre domande sui tag