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


122

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

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

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


11
Мне интересно, как вам вообще удалось так работать - у std∷exceptionвас нет конструктора с char*arg.
Hi-Angel

2
Мне интересно то же самое. Может, это нестандартное расширение MS на c ++? А может что-то новенькое в C ++ 14? В текущей документации сказано, что конструктор std :: exception не принимает никаких аргументов.
Крис Варт,

1
Да, но у std::stringнего есть неявный конструктор, который принимает const char*...
Брайс М. Демпси

6
@Chris Warth Похоже, что это часть закулисной реализации std::exceptionдочерних классов MS , и используется их версиями std::runtime_errorи std::logic_error. Помимо тех, что определены стандартом, версия MSVS <exception>также включает в себя еще два конструктора, один из которых принимает, (const char * const &)а другой принимает (const char * const &, int). Они используются для установки частной переменной const char * _Mywhat; если _Mywhat != nullptr, то по what()умолчанию возвращает его. Код, который полагается на него, вероятно, не переносится.
Джастин Тайм - Восстановить Монику

Ответы:


49

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

#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

1
боже, я искал, как сделать что-то подобное. Но, вероятно, изменит оператор >> на явную функцию, чтобы предотвратить чрезмерную (перегрузку оператора)
Роман Плашил,

3
какая разница между этим и std :: stringstream? Кажется, что он содержит поток строк, но не имеет (насколько я могу судить) никаких дополнительных функций.
matts1

2
Как правило, это не на 100% безопасный способ. Методы std :: stringstream могут вызывать исключение. Проблема довольно хорошо описана здесь: boost.org/community/error_handling.html
Артур П. Голубев

1
@ ArthurP.Golubev Но в этом случае экземпляр Formatter () также создает за кулисами строковый поток, что опять же может вызвать исключение. Так в чем разница?
Зузу Корнелиу

Единственная добавленная функция - это трюк ConvertToString и явное приведение к строке, что в любом случае приятно. ;)
Zuzu Corneliu

180

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


27

Существуют различные исключения , такие как 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);

Приведенная выше версия boost :: format не будет компилироваться без явного преобразования, например: runtime_error ((boost :: format ("Text% 1"% 2) .str ())). В C ++ 20 представлен формат std :: format, обеспечивающий аналогичную функциональность.
Digicrat

17

Следующий класс может оказаться весьма кстати:

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());

4
Плохая практика IMO, зачем использовать что-то подобное, когда уже есть стандартная библиотека, созданная для оптимизации?
Жан-Мари Кометс,

3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Жан-Мари Кометс,

4
throw std::runtime_error("Could not load config file " + configfile);(преобразовывая std::stringпри необходимости тот или иной аргумент в ).
Майк Сеймур,

9
@MikeSeymour Да, но это становится уродливее, если вам нужно поместить строки посередине и форматировать числа с определенной точностью и т.д.
Максим Егорушкин

2
@MikeSeymour Я могу согласиться с тем, что опубликованный мной код опережает свое время. В printfC ++ 11 неизбежно появятся переносимые типы и друзья. Буфер фиксированного размера - это и благословение, и проклятие: он не дает сбоев в ситуациях с нехваткой ресурсов, но может усечь сообщение. Я считаю обрезание сообщения об ошибке лучшим вариантом, чем отказ. Кроме того, удобство форматных строк было доказано многими разными языками. Но вы правы, это во многом дело вкуса.
Максим Егорушкин

11

Используйте оператор строкового литерала, если C ++ 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);

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


2
Я получил эту ошибку c ++ \ 7.3.0 \ bits \ exception.h | 63 | примечание: нет соответствующей функции для вызова 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>)
HaseeB Mir

Поведение, описанное @Shreevardhan, не определено в библиотеке std, хотя MSVC ++ скомпилирует его.
jochen

0

По-настоящему приятнее было бы создать класс (или классы) для исключений.

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

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

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

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

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

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

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

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

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

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

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

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

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

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


0

Возникла аналогичная проблема, когда создание пользовательских сообщений об ошибках для моих пользовательских исключений приводит к уродливому коду. Это было моим решением:

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

Это разделяет логику создания сообщений. Первоначально я думал о том, чтобы переопределить what (), но тогда вам нужно где-то записать свое сообщение. std :: runtime_error уже имеет внутренний буфер.


0

Может быть, это?

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

Он создает временный поток ostringstream, вызывает операторы << по мере необходимости, а затем вы заключаете его в круглые скобки и вызываете функцию .str () для оцененного результата (который является потоком ostring) для передачи временного std :: string конструктору из runtime_error.

Примечание: поток ostringstream и строка являются временными значениями r и поэтому выходят из области видимости после окончания этой строки. Конструктор вашего объекта исключения ДОЛЖЕН принимать входную строку, используя семантику копирования или (лучше) перемещения.

Дополнительно: я не обязательно считаю этот подход «лучшей практикой», но он действительно работает и может быть использован в крайнем случае. Одна из самых больших проблем заключается в том, что этот метод требует выделения кучи, поэтому оператор << может бросать. Вероятно, вы этого не хотите; однако, если вы попадете в это состояние, у вас, вероятно, будет больше проблем, о которых стоит беспокоиться!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.