Home » C++ » Howto throw std::exceptions with variable messages?

Howto throw std::exceptions with variable messages?

Posted by: admin November 29, 2017 Leave a comment

Questions:

This is an example of what I often do when I want to add some information to an exception:

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

Is there a nicer way to do it?

Answers:

Here is my solution:

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

Example:

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

Questions:
Answers:

The standard exceptions can be constructed from a 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);

Note that the base class std::exception can not be constructed thus; you have to use one of the concrete, derived classes.

Questions:
Answers:

There are different exceptions such as runtime_error, range_error, overflow_error, logic_error, etc.. You need to pass the string into its constructor, and you can concatenate whatever you want to your message. That’s just a string operation.

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

You can also use boost::format like this:

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

Questions:
Answers:

The following class my come quite handy:

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

Usage example:

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

Questions:
Answers:

Use string literal operator if C++14 (operator ""s)

using namespace std::string_literals;

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

or define your own if in C++11. For instance

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

Your throw statement will then look like this

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

which looks nice and clean.

Questions:
Answers:

A really nicer way would be creating a class (or classes) for the exceptions.

Something like:

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

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

The reason is that exceptions are much more than just transferring a string. Providing different classes for the errors, you give developers a chance to handle a particular error in a corresponded way (not just display an error message). People catching your exception can be as specific as they need if you use a hierarchy.

a) One may need to know the specific reason

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

a) another does not want to know details

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

You can find some inspiration on this topic in https://books.google.ru/books?id=6tjfmnKhT24C Chapter 9

Also, you can provide a custom message too, but be careful – it is not safe to compose a message with either std::string or std::stringstream or any other way which can cause an exception.

Generally, there is no difference whether you allocate memory (work with strings in C++ manner) in the constructor of the exception or just before throwing – std::bad_alloc exception can be thrown before the one which you really want.

So, a buffer allocated on the stack (like in Maxim’s answer) is a safer way.

It is explained very well at http://www.boost.org/community/error_handling.html

So, the nicer way would be a specific type of the exception and be avoiding composing the formatted string (at least when throwing).

Leave a Reply

Your email address will not be published. Required fields are marked *