Comment lancer des exceptions std :: avec des messages variables?

122

Voici un exemple de ce que je fais souvent lorsque je souhaite 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?

Ben
la source
11
Je me demande comment avez-vous réussi à travailler de cette façon - le std∷exceptionfait de ne pas avoir de constructeur avec char*arg.
Hi-Angel
2
Je me demande la même chose. Peut-être s'agit-il d'une extension MS non standard de C ++? Ou peut-être quelque chose de nouveau dans C ++ 14? La documentation actuelle indique que le constructeur std :: exception ne prend aucun argument.
Chris Warth
1
Oui, mais std::stringa un constructeur implicite qui prend un const char*...
Brice M. Dempsey
6
@Chris Warth Il semble faire partie de l'implémentation en coulisses des std::exceptionclasses enfants de MS , et est utilisé par leurs versions de std::runtime_erroret std::logic_error. En dehors de ceux définis par la norme, la version de MSVS <exception>comprend également deux autres constructeurs, l'un prenant (const char * const &)et l'autre prenant (const char * const &, int). Ils sont utilisés pour définir une variable privée, const char * _Mywhat; if _Mywhat != nullptr, puis le what()renvoie par défaut. Le code qui en dépend n'est probablement pas portable.
Justin Time - Réintègre Monica le

Réponses:

49

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
Torsten
la source
1
omg j'ai cherché comment faire quelque chose comme ça. Mais va probablement changer l'opérateur >> en fonction explicite pour éviter la surcharge (surcharge de l'opérateur)
Roman Plášil
3
quelle est la différence entre ceci et un std :: stringstream? Il semble contenir un stringstream, mais n'a (pour autant que je sache), aucune fonctionnalité supplémentaire.
matts1
2
Généralement, ce n'est pas un moyen sûr à 100%. Les méthodes std :: stringstream peuvent lever une exception.Le problème est assez bien décrit ici: boost.org/community/error_handling.html
Arthur P. Golubev
1
@ ArthurP.Golubev Mais dans ce cas, une instance Formatter () instancie également un flux de chaînes en arrière-plan, ce qui, encore une fois, pourrait lever une exception. Alors, quelle est la différence?
Zuzu Corneliu
La seule fonctionnalité ajoutée est l'astuce ConvertToString et le cast explicite en chaîne, ce qui est bien de toute façon. ;)
Zuzu Corneliu
180

Les exceptions standard peuvent être construites à partir d'un 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 nestd::exception peut pas être construite ainsi; vous devez utiliser l'une des classes dérivées concrètes.

Kerrek SB
la source
27

Il existe différentes exceptions comme runtime_error, range_error, overflow_error, logic_error, etc .. Vous devez passer la chaîne dans son constructeur, et vous pouvez concaténer tout ce que vous voulez à 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::formatcomme ceci:

throw std::runtime_error(boost::format("Error processing file %1") % fileName);
Neel Basu
la source
La version boost :: format ci-dessus ne se compilera pas sans une conversion explicite, c'est-à-dire: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). C ++ 20 introduit un format std :: qui fournira des fonctionnalités similaires.
Digicrat
17

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());
Maxim Egorushkin
la source
4
Mauvaise pratique IMO, pourquoi utiliser quelque chose comme ça alors qu'il existe déjà une bibliothèque standard conçue pour l'optimisation?
Jean-Marie Comets le
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);(conversion de l'un ou l'autre argument en std::stringsi nécessaire).
Mike Seymour le
9
@MikeSeymour Oui, mais cela devient plus moche si vous avez besoin de mettre des chaînes au milieu et de formater des nombres avec une certaine précision, etc. Il est difficile de battre une bonne vieille chaîne de format en termes de clarté.
Maxim Egorushkin le
2
@MikeSeymour Je peux accepter que le code que j'ai publié soit en avance sur son temps. La sécurité de type portable printfet les amis sont imminents dans C ++ 11. Un tampon de taille fixe est à la fois une bénédiction et une malédiction: il n'échoue pas dans les situations de faibles ressources mais peut tronquer le message. Je considère que la troncature d'un message d'erreur est une meilleure option que l'échec. En outre, la commodité des chaînes de format a été prouvée par de nombreux langages différents. Mais vous avez raison, c'est en grande partie une question de goût.
Maxim Egorushkin
11

Utilisez 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éfinissez le vôtre 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 bien et propre.

Shreevardhan
la source
2
J'ai eu cette erreur c ++ \ 7.3.0 \ bits \ exception.h | 63 | note: pas de fonction correspondante pour l'appel à 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir
Le comportement décrit par @Shreevardhan n'est pas défini dans la bibliothèque std, bien que MSVC ++ le compilera.
jochen
0

Une manière 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 préférables 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 d'une manière correspondante (pas seulement d'afficher un message d'erreur). Les personnes qui interceptent votre exception peuvent être aussi spécifiques qu'elles en ont besoin 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

En outre, vous pouvez fournir un message personnalisé aussi, mais attention - il n'est pas sûr de composer un message soit avec std::stringou std::stringstreamou tout autre moyen qui peut provoquer une exception .

Généralement, il n'y a aucune 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 - l' std::bad_allocexception peut être levée avant celle que vous voulez vraiment.

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

Cela est très bien expliqué sur http://www.boost.org/community/error_handling.html

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

Arthur P. Golubev
la source
0

Ran dans un problème similaire, en ce que la création de messages d'erreur personnalisés pour mes exceptions personnalisées rend le code laid. C'était ma solution:

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
     }
}

Cela sépare la logique de création des messages. J'avais initialement pensé à remplacer what (), mais vous devez ensuite capturer votre message quelque part. std :: runtime_error a déjà un tampon interne.

vélos
la source
0

Peut être ça?

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

Il crée un ostringstream temporaire, appelle les opérateurs << si nécessaire, puis vous mettez cela entre crochets et appelez la fonction .str () sur le résultat évalué (qui est un ostringstream) pour passer un std :: string temporaire au constructeur de runtime_error.

Remarque: l'ostringstream et la chaîne sont des temporaires de valeur r et sortent donc de la portée après la fin de cette ligne. Le constructeur de votre objet d'exception DOIT prendre la chaîne d'entrée en utilisant la sémantique copy ou (meilleure) move.

Supplémentaire: je ne considère pas nécessairement cette approche comme une «meilleure pratique», mais elle fonctionne et peut être utilisée à la rigueur. L'un des plus gros problèmes est que cette méthode nécessite des allocations de tas et que l'opérateur << peut lancer. Vous ne voulez probablement pas que cela se produise; Cependant, si vous entrez dans cet état, vous aurez probablement beaucoup plus de problèmes à craindre!

Evilrix
la source