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?
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
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.
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);
_
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());
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.
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).