Как бросить std :: исключения с переменными сообщениями?

Это пример того, что я часто делаю, когда хочу добавить некоторую информацию в исключение:

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

Есть ли лучший способ сделать это?

Ответ 1

Вот мое решение:

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

Пример:

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

Ответ 2

Стандартные исключения могут быть построены из 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);

Обратите внимание, что базовый класс std::exception не может быть построен таким образом; вы должны использовать один из конкретных, производных классов.

Ответ 3

Существуют разные исключения, такие как runtime_error, range_error, overflow_error, logic_error и т.д. Вам нужно передать строку в свой конструктор, и вы можете связать все, что хотите от своего сообщения. Это просто операция с строкой.

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

Вы также можете использовать boost::format следующим образом:

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

Ответ 4

Следующий класс может пригодиться:

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

Пример использования:

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

Ответ 5

Использовать оператор строкового литерала, если С++ 14 (operator ""s)

using namespace std::string_literals;

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

или определить свой собственный, если в С++ 11. Например,

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

Тогда ваш оператор throw будет выглядеть следующим образом

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

который выглядит красиво и чисто.

Ответ 6

Лучше всего создать класс (или классы) для исключений.

Что-то вроде:

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

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

Причина в том, что исключения гораздо больше, чем просто перенос строки. Предоставляя разные классы для ошибок, вы даете разработчикам возможность обрабатывать определенную ошибку соответствующим образом (а не просто отображать сообщение об ошибке). Люди, улавливающие ваше исключение, могут быть такими же конкретными, какими они нужны, если вы используете иерархию.

a) Возможно, потребуется знать конкретную причину

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

a) другой не хочет знать подробности

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

Вы можете найти вдохновение в этой теме в https://books.google.ru/books?id=6tjfmnKhT24C Глава 9

Кроме того, вы также можете предоставить настраиваемое сообщение, но будьте осторожны - небезопасно составлять сообщение с помощью std::string или std::stringstream или любым другим способом, который может вызвать исключение,

Как правило, нет никакой разницы, выделяете ли вы выделение памяти (работа со строками в стиле С++) в конструкторе исключения или перед тем, как бросать - исключение std::bad_alloc может быть выбрано перед тем, которое вы действительно хотите.

Таким образом, буфер, выделенный в стеке (например, в ответе Максима), более безопасен.

Это очень хорошо объясняется в http://www.boost.org/community/error_handling.html

Итак, лучший способ - это конкретный тип исключения и избегать компоновки форматированной строки (по крайней мере, при метании).