Come lanciare std :: eccezioni con messaggi variabili?


121

Questo è un esempio di ciò che faccio spesso quando voglio aggiungere alcune informazioni a un'eccezione:

std::stringstream errMsg;
errMsg << "Could not load config file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

C'è un modo più carino per farlo?


11
Mi chiedo come hai fatto a lavorare in questo modo - std∷exceptionnon hai un costruttore con char*arg.
Hi-Angel

2
Mi chiedo la stessa cosa. Forse è un'estensione MS non standard a c ++? O forse qualcosa di nuovo in C ++ 14? La documentazione corrente dice che il costruttore std :: exception non accetta alcun argomento.
Chris Warth

1
Sì, ma std::stringha un costruttore implicito che prende un const char*...
Brice M. Dempsey

6
@Chris Warth Sembra che faccia parte dell'implementazione dietro le quinte delle std::exceptionclassi figlie di MS ed è usato dalle loro versioni di std::runtime_errore std::logic_error. Oltre a quelli definiti dallo standard, la versione di MSVS <exception>include anche altri due costruttori, uno take (const char * const &)e l'altro taking (const char * const &, int). Sono usati per impostare una variabile privata const char * _Mywhat,; se _Mywhat != nullptr, quindi viene what()ripristinato per impostazione predefinita. Il codice che si basa su di esso probabilmente non è portabile.
Justin Time - Ripristina Monica il

Risposte:


49

Ecco la mia soluzione:

#include <stdexcept>
#include <sstream>

class Formatter
{
public:
    Formatter() {}
    ~Formatter() {}

    template <typename Type>
    Formatter & operator << (const Type & value)
    {
        stream_ << value;
        return *this;
    }

    std::string str() const         { return stream_.str(); }
    operator std::string () const   { return stream_.str(); }

    enum ConvertToString 
    {
        to_str
    };
    std::string operator >> (ConvertToString) { return stream_.str(); }

private:
    std::stringstream stream_;

    Formatter(const Formatter &);
    Formatter & operator = (Formatter &);
};

Esempio:

throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData);   // implicitly cast to std::string
throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData >> Formatter::to_str);    // explicitly cast to std::string

1
omg ho cercato come fare qualcosa di simile. Ma probabilmente cambierà l'operatore >> con la funzione esplicita per evitare il sovraccarico dell'operatore
Roman Plášil

3
qual è la differenza tra questo e uno std :: stringstream? Sembra contenere un flusso di stringhe, ma (per quanto ne so) non ha funzionalità extra.
matts1

2
In generale, non è un modo sicuro al 100%. I metodi std :: stringstream possono generare un'eccezione.Il problema è abbastanza buono descritto qui: boost.org/community/error_handling.html
Arthur P. Golubev

1
@ ArthurP.Golubev Ma in questo caso, un'istanza di Formatter () crea anche un'istanza di un flusso di stringhe dietro le quinte, che di nuovo potrebbe generare un'eccezione. Allora qual è la differenza?
Zuzu Corneliu

L'unica funzionalità aggiunta è il trucco ConvertToString e il cast esplicito alla stringa, il che è comunque bello. ;)
Zuzu Corneliu

179

Le eccezioni standard possono essere costruite da std::string:

#include <stdexcept>

char const * configfile = "hardcode.cfg";
std::string const anotherfile = get_file();

throw std::runtime_error(std::string("Failed: ") + configfile);
throw std::runtime_error("Error: " + anotherfile);

Notare che la classe base nonstd::exception può essere costruita in questo modo; devi usare una delle classi derivate concrete.


27

Ci sono diverse eccezioni come runtime_error, range_error, overflow_error, logic_error, ecc .. È necessario passare la stringa in suo costruttore, e si può concatenare quello che vuoi al tuo messaggio. Questa è solo un'operazione su stringhe.

std::string errorMessage = std::string("Error: on file ")+fileName;
throw std::runtime_error(errorMessage);

Puoi anche usare in boost::formatquesto modo:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);

La versione boost :: format sopra non verrà compilata senza una conversione esplicita, ad esempio: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 introduce un formato std :: che fornirà funzionalità simili.
Digicrat

17

La seguente classe potrebbe tornare molto utile:

struct Error : std::exception
{
    char text[1000];

    Error(char const* fmt, ...) __attribute__((format(printf,2,3))) {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(text, sizeof text, fmt, ap);
        va_end(ap);
    }

    char const* what() const throw() { return text; }
};

Esempio di utilizzo:

throw Error("Could not load config file '%s'", configfile.c_str());

4
Cattiva pratica IMO, perché usare qualcosa di simile quando esiste già una libreria standard creata per l'ottimizzazione?
Jean-Marie Comets

3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Jean-Marie Comets

4
throw std::runtime_error("Could not load config file " + configfile);(convertire l'uno o l'altro argomento in std::stringse necessario).
Mike Seymour

9
@MikeSeymour Sì, ma diventa più brutto se è necessario inserire stringhe nel mezzo e formattare i numeri con una certa precisione, ecc. È difficile battere una buona vecchia stringa di formato in termini di chiarezza.
Maxim Egorushkin

2
@ MikeSeymour Potrei essere d'accordo che il codice che ho pubblicato potrebbe essere in anticipo sui tempi. Il typesafe portabile printfe gli amici sono imminenti in C ++ 11. Il buffer a dimensione fissa è sia una benedizione che una maledizione: non fallisce in situazioni di scarse risorse ma può troncare il messaggio. Considero troncare un messaggio di errore un'opzione migliore rispetto al fallimento. Inoltre, la comodità delle stringhe di formato è stata dimostrata da molti linguaggi diversi. Ma hai ragione, è soprattutto una questione di gusti.
Maxim Egorushkin

11

Usa l'operatore letterale stringa se C ++ 14 ( operator ""s)

using namespace std::string_literals;

throw std::exception("Could not load config file '"s + configfile + "'"s);

o definisci il tuo se in C ++ 11. Per esempio

std::string operator ""_s(const char * str, std::size_t len) {
    return std::string(str, str + len);
}

La tua dichiarazione di lancio sarà quindi simile a questa

throw std::exception("Could not load config file '"_s + configfile + "'"_s);

che sembra carino e pulito.


2
Ho ricevuto questo errore c ++ \ 7.3.0 \ bits \ exception.h | 63 | nota: nessuna funzione corrispondente per la chiamata a 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir

Il comportamento descritto da @Shreevardhan non è definito nella libreria std, sebbene MSVC ++ lo compilerà.
Jochen

0

Un modo davvero più carino sarebbe creare una o più classi per le eccezioni.

Qualcosa di simile a:

class ConfigurationError : public std::exception {
public:
    ConfigurationError();
};

class ConfigurationLoadError : public ConfigurationError {
public:
    ConfigurationLoadError(std::string & filename);
};

Il motivo è che le eccezioni sono molto più preferibili del semplice trasferimento di una stringa. Fornendo classi diverse per gli errori, dai agli sviluppatori la possibilità di gestire un errore particolare in modo corrispondente (non solo visualizzare un messaggio di errore). Le persone che rilevano la tua eccezione possono essere specifiche quanto necessitano se utilizzi una gerarchia.

a) Potrebbe essere necessario conoscere il motivo specifico

} catch (const ConfigurationLoadError & ex) {
// ...
} catch (const ConfigurationError & ex) {

a) un altro non vuole conoscere i dettagli

} catch (const std::exception & ex) {

Puoi trovare ispirazione su questo argomento in https://books.google.ru/books?id=6tjfmnKhT24C Capitolo 9

Inoltre, puoi fornire anche un messaggio personalizzato, ma fai attenzione: non è sicuro comporre un messaggio con std::stringo std::stringstreamo in qualsiasi altro modo che possa causare un'eccezione .

In generale, non c'è differenza se si alloca memoria (si lavora con le stringhe in modo C ++) nel costruttore dell'eccezione o appena prima di lanciare - l' std::bad_alloceccezione può essere lanciata prima di quella che si vuole veramente.

Quindi, un buffer allocato nello stack (come nella risposta di Maxim) è un modo più sicuro.

È spiegato molto bene su http://www.boost.org/community/error_handling.html

Quindi, il modo migliore sarebbe un tipo specifico di eccezione ed evitare di comporre la stringa formattata (almeno durante il lancio).


0

Mi sono imbattuto in un problema simile, in quanto la creazione di messaggi di errore personalizzati per le mie eccezioni personalizzate rende il codice brutto. Questa era la mia soluzione:

class MyRunTimeException: public std::runtime_error
{
public:
      MyRunTimeException(const std::string &filename):std::runtime_error(GetMessage(filename)) {}
private:
      static std::string GetMessage(const std::string &filename)
     {
           // Do your message formatting here. 
           // The benefit of returning std::string, is that the compiler will make sure the buffer is good for the length of the constructor call
           // You can use a local std::ostringstream here, and return os.str()
           // Without worrying that the memory is out of scope. It'll get copied
           // You also can create multiple GetMessage functions that take all sorts of objects and add multiple constructors for your exception
     }
}

Questo separa la logica per la creazione dei messaggi. Inizialmente avevo pensato di sovrascrivere what (), ma poi devi catturare il tuo messaggio da qualche parte. std :: runtime_error ha già un buffer interno.


0

Forse questo?

throw std::runtime_error(
    (std::ostringstream()
        << "Could not load config file '"
        << configfile
        << "'"
    ).str()
);

Crea un ostringstream temporaneo, chiama gli operatori << come necessario, quindi lo racchiudi tra parentesi tonde e chiama la funzione .str () sul risultato valutato (che è un ostringstream) per passare una stringa std :: temporanea al costruttore di runtime_error.

Nota: l'ostringstream e la stringa sono temporanei con valore r e quindi escono dall'ambito al termine di questa riga. Il costruttore del tuo oggetto eccezione DEVE prendere la stringa di input usando la semantica copia o (meglio) sposta.

Aggiuntivo: non considero necessariamente questo approccio "best practice", ma funziona e può essere utilizzato in un attimo. Uno dei problemi maggiori è che questo metodo richiede allocazioni di heap e quindi l'operatore << può lanciare. Probabilmente non vuoi che ciò accada; tuttavia, se entri in quello stato, probabilmente hai molti più problemi di cui preoccuparti!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.