LINUX.ORG.RU

Хочу всеядный класс исключений, который не порождает сам исключения :)

 


2

3

Вопрос про хорошие пользовательские классы исключений в Си++.

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

Это конечно не какой-нибудь дамп многострочный, который наверное должен создаваться не через e.what() а через что-то специальное. Но хотелось бы чтобы в исключение можно было напихать как минимум char*, string, и любые другие от bool до double нативные типы. Потому что кажется что это было бы очень удобно с точки зрения использования:

throw SomeException() << "blabla " << "index " << i << " array_size" << arr.size() и т.п.

Ну и вот теперь вопрос - кто-нибудь такое делал, и как вы это сделали?

Это достаточно легко сделать средствами STL, берём std::string, берем std::to_string() и ура, если для числовых типов обернуть шаблоном, то вероятно потребовался бы один метод на все случаи :)

Но по-моему так нестоит в исключениях. Исключения это типа такие классы которые возможно создаются в экстремальных условиях нехватки памяти, и тогда

try{
  throw SomeException();
} catch (const SomeException &e) {
  //можем сюда не попасть.
}

Да, да, чтобы такого не было можно закостылить внутри методов иключения, в которых может породиться другое исключение свой try-catch - но это как-то не эстетично, и наверерное нагрузочно на стек вызовов, а хочется чего-то легкого, что имело бы больше шансов на удачное создание, жизнь и уничтожение, когда осталось мало ресурсов на стеке программы, в куче и тому подобном...

Вопросы

Можно ли придумать что-то более лучшее чем:

1. std::string или динамически расширяемый char * заменить исключительно на char * константной длинны (массив) и в случае захода за длинну просто не заполнять его дальше, а конец массива сделать многоточием - что будет обозначать что это не весь текст исключения.

2. Взамен to_string напрашивается что-то из std::sprintf(buf, «%ld», value) - https://ru.cppreference.com/w/cpp/io/c/fprintf но к сожалению не знаю/не_помню и по ссылке не нашел, что будет если в buf не хватит размера для преобразования числа в строку?

3. И немного глупый вопрос про низкий уровень: если в коде есть строка

A << val1 << val2 << val3 << val4
то как тратится программный стек на это дело?

Правильно что хоть сколько в ряд таких операторов записать то стек не потратится больше чем на размещения вызываемого метода + входящей в него переменной?

Иными словами, где-то на хабре находил статью что например switch() оператор - бывает если условий очень много и добавить еще один условный переход в него - то могут пойти лютые баги, из-за того что стек переполнился и т.п. Но со свичем видимо играет то что он должен сразу все свои ветви разместить на стеке

А вот это грубо говоря просто пошагово должно быть: A << val1 << val2 << val3 << val4 это тоже самое что int a=0; a+=f(); a+=f(); a+=f(); т.е. хоть «беcконечность» таких a+=f(3); сделай - это не покрошит программу, т.к. не переполнит программный стек (ну разве что, безопасно для рантайма,переполнит тип int).



Последнее исправление: bober (всего исправлений: 2)

Что-то типа такого?

class MyException: public std::runtime_error {
public:
  MyException(const json& data): std::runtime_error(data.toString()), data_(data) {}

  json data() const {  return data_; }
  

private:
  json data_;
};

+- апи либы, которая предоставляет json.

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

q0tw4 ★★★★
()

boost::exception ровно об этом, хотя мне их реализация не очень нравится.

snizovtsev ★★★★★
()
Ответ на: комментарий от q0tw4

Да, тупо json-чик в классе исключения удобнее. Но тут нужно помнить, что это исключение и по минимум аллоцировать память и не кидать новое. В случае ошибок надо всегда аккуратно заменять информацию чем-то вроде «Not Available».

snizovtsev ★★★★★
()
Ответ на: комментарий от snizovtsev

Желающие жсонить могут проследовать в JS или питон. Там им в принципе будет комфортнее.

q0tw4 ★★★★
()

А самый пуленепробиваемый способ - взять гугловую либу breakpad, и с её помощью в момент выбрасывания исключения тупо снэпшотить ею стек. Потом скормить тулзам и debuginfo, и получить любую информацию, даже если о ней раньше не подумали. Можно даже снэпшотить не на каждый throw, а только на тот, что дойдёт до нужного catch, т.к. обычно c++ abi в два прохода стек разворачивает. Так будет и много отладочной инфы, и отсутствие оверхеда когда он не нужен. Я хотел однажды всё это обернуть в удобный фреймворк, да так и не обернул.
Итого проще исключения не использовать, а использовать тип Result ala Rust.

snizovtsev ★★★★★
()
Ответ на: комментарий от annulen

Не вариант. Нет сахара типа let Some x = maybeGetX(). Итого в коде optional постоянно используется со стрелками и звездами, что ограничивает его полезность вариантами, в которых неудача функции является штатной ситуацией.

q0tw4 ★★★★
()

Ну можно так и сделать:

class VerboseException : public std::exception
{
public:
    template <class T>
    VerboseException & operator<<(T &&data) {
        try {
            oss << std::forward<T>(data);
        } catch (...) {
            fuckup = true;
        }
        return *this;
    }

    virtual const char * what() const override {
        if (fuckup) {
            return "Verbose exception message is not available";
        }
        value = oss.str();
        return value.c_str();
    }

private:
    std::ostringstream oss;
    std::string value;
    bool fuckup = false;
};

что будет если в buf не хватит размера для преобразования числа в строку?

Вывод обрежется справа.

то как тратится программный стек на это дело?

Это последовательные вызовы, а не рекурсивные.

Но я бы не беспокоился об окончании стека или памяти, только второе можно перехватить, но обработать с пользой получится вряд ли. Это если это не что-то важное, что не должно падать, но я так не думаю.

xaizek ★★★★★
()
Ответ на: комментарий от q0tw4

Итого в коде optional постоянно используется со стрелками и звездами

Проверил один раз и разыменовал, в чем проблема

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от xaizek

Интересно, а можно ли узнать размер своего стека и, при подходе к его переполнению, увеличить себе стек? Ну и с мемори, можно например свой гарбач коллектор по кешам запускать когда она кончается.

q0tw4 ★★★★
()
Ответ на: комментарий от xaizek

Но я бы не беспокоился об окончании стека или памяти, только второе можно перехватить, но обработать с пользой получится вряд ли. Это если это не что-то важное, что не должно падать, но я так не думаю.

Как насчёт сервера, который аллоцирует некоторое количество памяти для каждого клиента? Тогда при bad_alloc на очередном клиенте можно просто его кикать и не принимать новых, пока старые не отключатся, а не падать с грохотом уводя всё в даун.

d_a ★★★★★
()
Ответ на: комментарий от q0tw4

Что-то вроде такого разве не помогает?

template<typename T, typename Lambda>
void on_value(optional<T> & opt, Lambda && l) {
  if(opt) lambda(*opt);
}
...
auto ret_val = some_func_with_optional_as_result(...);
on_value(ret_val, [](auto & v) { ... });

eao197 ★★★★★
()
Ответ на: комментарий от q0tw4

По крайней мере это лучше, чем исключение, которое вообще не видно по месту вызова, пока не воткнули try

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)
Ответ на: комментарий от q0tw4

Или забыл проверить, потому что уверен, что там 100% всё норм

Кстати, не самый простой способ выстрелить себе в ногу в С++

annulen ★★★★★
()
Ответ на: комментарий от d_a

Это если это не что-то важное, что не должно падать

Просто в этом случае и понятно как действовать, а в общем при нехватке памяти можно только руками развести.

xaizek ★★★★★
()
Ответ на: комментарий от q0tw4

А можно перейти на rust.

А можно вспомнить, что речь про C++ и не говорить глупости.

eao197 ★★★★★
()
Ответ на: комментарий от q0tw4

Платформо-специфичными способами может и можно, а увеличивается он обычно только у основного потока, а у остальных он фиксирован.

Ну и с мемори, можно например свой гарбач коллектор по кешам запускать когда она кончается.

В каких-то приложениях можно. Я просто к тому, что ТС скорее всего заботится о чём-то, что для него либо не проблема, либо не имеет адекватного решения.

xaizek ★★★★★
()

то как тратится программный стек на это дело?

Тебе наплевать как тратится стек. Поскольку если во время форматирования исключения кончается стек, значит есть проблема со стеком которая могла бы в любой другой момент выстрелить, так пусть стреляет, она всяко важнее текущего исключения. Аналогично с кучей.

А что касается кастомного исключения, то главная проблема с ним - исключения должны быть nothrow copyable. Поэтому std::string в них напрямую запихивать, вообще говоря, нельзя и надо либо обёртывать в какой-нибудь shared_ptr, либо согласиться на terminate при ошибке в копировании исключения.

Да, реализация в первом приближении - простой template<class T> MyException& operator<<(MyException& e, const T& o) { e.message += std::to_string(o); return e; }, не надо тут никаких sprintf и специализаций. Во втором приближении нужно подумать как ты обрабатываешь исключения - если там сколь либо сложная логика сверх печати ошибки, правильнее будет использовать cтруктурированные исключения с раздельными полями данных.

slovazap ★★★★★
()
Ответ на: комментарий от RazrFalcon

Я так понял, что на ближайшем заседании комитета будут обсуждать включение в C++20. Не знаю, правда, каковы шансы, но есть уже несколько ревизий предложения, что является хорошим знаком.

eao197 ★★★★★
()
Ответ на: комментарий от deep-purple

джампы - опасная штука. Мало ли что в деструкторах случится. Выживать в экстремальных условиях джампом так же странно, как пытаться зарековиритися на битой плашке памяти.

q0tw4 ★★★★
()
Ответ на: комментарий от hateyoufeel

Запилят. И персонажи, вроде Xintrea или Iron_Bug будут еще громче вопить о том, насколько испоганили язык. А подвывать им будут персонажи, производящие вот такой замечательный код на C++ (взято отсюда):

void Methods :: ofNelder_Mid()
{
    int i, j, k;
    D modz = 0., p, eps = 1e-3;
    D FR, FN, F0, FE, FNm1, FC;
    const int N = 2;
    D *Co = new D[N];
    D *Eo = new D[N];
    D *Ro = new D[N];
    D *Xo = new D[N];
    D **Xx = new D*[N];
    D **dz = new D*[N];
    for(i=0;i<N;i++)
    {
        dz[i] = new D[N];
        Xx[i] = new D[N+1];
    }
...
    delete[]Co;
    delete[]Xo;
    delete[]Ro;
    delete[]Eo;
    for(i=0;i<N;i++)
    {
        delete[]dz[i];
        delete[]Xx[i];
    }
}

eao197 ★★★★★
()

Исключения это типа такие классы которые возможно создаются в экстремальных условиях нехватки памяти

Нехватку памяти не следует рассматривать как исключение, которое можно обработать. Ее нужно рассматривать типа как panic в Golang и просто крашить программу. К счатью, в C++ это легко сделать через правильно реализованный new_handler, специально придуманный для такой ситуации.

rupert ★★★★★
()
Ответ на: комментарий от rupert

Вряд ли. Очень удобно когда приложение фризится до момента когда не появится свободная память для дальнейшей работы. И тогда никакие данные не теряются.

Как например это делает ВиртуалБокс при загрузке В.М.: процесс загрузки не теряется, ф.с. не остаётся в неконсистентном состоянии, в.м. просто останавливается и выводится диалог что мол дальнейшая работа не возможна, освободите память и пробудите в.м. для дальнейшей работы.

bonta ★★★★★
()
Последнее исправление: bonta (всего исправлений: 1)
Ответ на: комментарий от rupert

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

Скорее лишь показывает почему вы не видите, по меньшей мере, двух серьезных косяков в собственной точке зрения:

* далеко не для всех типов приложений допустим неизбежный краш при обнаружении нехватки памяти;

* в Go присутствует функция recover(), благодаря которой вызванный panic вовсе не гарантирует завершение работы приложения.

А не видите вы этого потому, что у вас проблемы с логикой из-за чего вы считаете возможным защищать свою точку зрения аргументами «eao197 дурак».

eao197 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.