Як викинути std :: винятки зі змінними повідомленнями?


121

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

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

Чи є кращий спосіб це зробити?


10
Мені цікаво, як вам навіть вдалося працювати таким чином - std∷exceptionнемає конструктора з char*аргументом.
Привіт-Ангел

2
Мені цікаво те саме. Можливо, це нестандартне розширення MS на c ++? А може, щось нове в C ++ 14? Поточна документація говорить, що конструктор std :: виключення не бере жодних аргументів.
Кріс Уорт

1
Так, але std::stringмає неявний конструктор, який займає const char*...
Бріс М. Демпсі

6
@Chris Warth Він, схоже, є частиною позапрограшної реалізації MS- std::exceptionкласів для дітей, і використовується їх версіями 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
omg Я дивився, як зробити щось подібне. Але, ймовірно, зміниться оператор >> на явну функцію, щоб запобігти перевантаженню (перевантаження оператора)
Roman Plášil

3
яка різниця між цим і std :: stringstream? Схоже, він містить рядковий потік, але не має (наскільки я можу) жодної додаткової функціональності.
matts1

2
Як правило, це не 100% безпечний спосіб. std :: stringstream методи можуть кинути виняток. Проблема досить добре описана тут: boost.org/community/error_handling.html
Артур П. Голубев

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

Єдиний доданий функціонал - трюк ConvertToString та явний переклад на рядок, що все одно приємно. ;)
Зузу Корнеліу

178

Стандартні винятки можуть бути побудовані з 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 :: формату вище не буде компілюватися без явного перетворення, тобто: runtime_error ((boost :: format ("Текст% 1"% 2) .str ())). C ++ 20 представляє формат std ::, який надаватиме аналогічні функції.
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
Погана практика ІМО, навіщо використовувати щось подібне, коли вже існує стандартна бібліотека, яка створена для оптимізації?
Жан-Марі Комець

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 Можу погодитися, що код, який я опублікував, може бути випереджає його час. Портально безпечні printfта друзі неминучі в C ++ 11. Буфер з фіксованим розміром є і благом, і прокляттям: він не виходить із ладу в ситуаціях з невеликим ресурсом, але може обрізати повідомлення. Я вважаю обрізання повідомлення про помилку кращим варіантом, ніж невдале. Також зручність рядків формату було доведено багатьма різними мовами. Але ви праві, це багато в чому справа смаку.
Максим Єгорушкін

11

Використовувати оператор рядкових літер, якщо C ++ 14 ( operator ""s)

using namespace std::string_literals;

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

або визначте власне, якщо в C ++ 11. Наприклад

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

Ваша заява про кидок буде виглядати приблизно так

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

що виглядає красиво і чисто.


2
Я отримав цю помилку c ++ \ 7.3.0 \ біт \ виключення.h | 63 | Примітка: немає функції узгодження для виклику до 'std :: виключення :: виняток (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
     }
}

Це розділяє логіку створення повідомлень. Я спочатку замислювався над тим, щоб змінити, що (), але потім вам доведеться десь захопити своє повідомлення. std :: runtime_error вже має внутрішній буфер.


0

Може, це?

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

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

Примітка: ostringstream і string є тимчасовими значеннями r, тому вони виходять із рамки після закінчення цього рядка. Конструктор вашого об’єкта винятку ОБОВ'ЯЗКОВО повинен приймати рядок введення, використовуючи копію або (краще) переміщення семантики.

Додатково: Я не обов'язково вважаю цей підхід "найкращою практикою", але він працює і може бути використаний на дрібниці. Одне з найбільших проблем полягає в тому, що цей метод вимагає виділення купи і тому оператор << може кидати. Ви, мабуть, не хочете, щоб це сталося; однак, якщо ви потрапите в цей стан, у вас, ймовірно, є більше проблем, щоб турбуватися!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.