web-dev-qa-db-fra.com

Comment lancer std :: exceptions avec des messages variables?

Voici un exemple de ce que je fais souvent lorsque je veux ajouter des informations à une exception:

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

Y a-t-il une meilleure façon de le faire?

98
Ben

Voici ma solution:

#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 &);
};

Exemple:

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
39
Torsten

Les exceptions standard peuvent être construites à partir de 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);

Notez que la classe de base std::exception peut not être construite ainsi; vous devez utiliser l'une des classes dérivées concrètes.

152
Kerrek SB

Il existe différentes exceptions, telles que _runtime_error_, _range_error_, _overflow_error_, _logic_error_, etc. à votre message. C'est juste une opération de chaîne.

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

Vous pouvez également utiliser boost::format comme ceci:

_throw std::runtime_error(boost::format("Error processing file %1") % fileName);
_
24
Neel Basu

La classe suivante peut être très 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; }
};

Exemple d'utilisation:

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

Utiliser l'opérateur littéral de chaîne si C++ 14 (operator ""s)

using namespace std::string_literals;

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

ou définir votre propre si en C++ 11. Par exemple

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

Votre déclaration de lancer ressemblera alors à ceci

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

qui a l'air gentil et propre.

11
Shreevardhan

Une façon vraiment plus agréable serait de créer une classe (ou des classes) pour les exceptions.

Quelque chose comme:

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

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

La raison en est que les exceptions sont bien plus que le simple transfert d'une chaîne. En fournissant différentes classes pour les erreurs, vous donnez aux développeurs une chance de gérer une erreur particulière de manière correspondante (ne pas afficher uniquement un message d'erreur). Les personnes qui détectent votre exception peuvent être aussi spécifiques que nécessaire si vous utilisez une hiérarchie.

a) On peut avoir besoin de connaître la raison spécifique

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

a) un autre ne veut pas connaître les détails

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

Vous pouvez trouver de l'inspiration sur ce sujet dans https://books.google.ru/books?id=6tjfmnKhT24C Chapitre 9

Vous pouvez également fournir un message personnalisé, mais soyez prudent - il n’est pas prudent de composer un message avec std::string ou std::stringstream ou par tout autre moyen provoquer une exception .

Généralement, il n'y a pas de différence que vous allouiez de la mémoire (travaillez avec des chaînes de manière C++) dans le constructeur de l'exception ou juste avant de lancer - std::bad_alloc exception peut être renvoyée avant celle que vous voulez réellement.

Ainsi, un tampon alloué sur la pile (comme dans la réponse de Maxim) est un moyen plus sûr.

C'est très bien expliqué à http://www.boost.org/community/error_handling.html

Ainsi, la meilleure façon serait un type spécifique de l'exception et éviterait de composer la chaîne formatée (au moins lors du lancement).

0
Arthur P. Golubev