LINUX.ORG.RU

exceptions в C++

 ,


5

9

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

Кто-нибудь может накидать каких-нибудь технических статей на тему как следует или не следует применять эксепшены в плюсах?

UPD:
Из любопытного
почитать (стр. 32)
посмотреть

★★★★★

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

Ответ на: комментарий от eao197

Только оставайтесь в рамках C++

Что именно мешает в крестах функцию вида

(|) : a -> a -> a
заменить на
(|) : result a -> result a -> result a

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

Ну так сделайте. Как сделаете посмотрим. Поржем.

Это был вопрос.

Если в крестах есть функция вида

a -> a
, что мешает определить функцию
b -> b
. А ржать в цирке будешь.

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

что мешает определить функцию

ну что тебе мешает-то ? определи, покажи нам мастер-класс

anonymous
()
Ответ на: комментарий от Freyr69

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

PS. В цирке клоуны хотя бы профессиональные.

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

Код покажите, грамотный вы наш.

На чем? На крестах? Так вопрос и был про то, что в крестах такого убогого, что они не позволяют переопределить функцию для алгебраического типа.

В расте-то с этим нет проблем. Тейлганнер вона говорит, что на крестах тоже можно, но код не пишет.

https://github.com/Geal/nom

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

На чем? На крестах?

В теме, посвященной использованию исключений на C++, было бы логично увидеть код на C++.

Так вопрос и был про то, что в крестах такого убогого, что они не позволяют переопределить функцию для алгебраического типа.

Очевидно, что C++ вы не знаете, но пытаетесь влезть в C++ со своим мнением о том, как делать «правильно».

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

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

Как угодно.

Ну я не согласен. Собственно, на этом можно остановиться, но всё-таки попробую последний раз: с базовым использованием перечисленных штук (PM, ADT и трейты) у меня при изучении раста проблем не было. Думаю, что у тебя тоже, но неужели не было моментов, когда понимать «как это, блин, сделать?!» приходит не сразу? Причём дело не в том, что надо почитать мануал и разобраться, а в том, что правила, вроде, понимаешь, но оно не складывается.

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

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

Ну а как быть с перегрузкой операторов? Как писать вот так:

Я согласен с tailgunner, что практически всегда можно записать не хуже. Но соглашусь и с тем, что так получается не всегда. Собственно, паника в расте и Go как раз следствие того, что не везде такой подход удачно ложится. Собственно, я не против иметь исключения, правда пока не определился хватит ли их в несколько «урезанном» виде (типа паники). Другое дело, что предпочитаю когда (хотя бы) стандартная библиотека не навязывает их использование.

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

Судя по всему, мы говорим о разных вещах. Я про набор фич, а не сложность их понимания.

Есть много проблем с растом, которые ни я, ни другие, не могут пока решить. Типа удобного получения ссылки из Rc<RefCell>, что очень часть используется в деревьях. Ну и borrow checker довольно туповат, когда дело доходит до сложных конструкций. Тем не менее, я не испытываю проблем при написании rust кода.

В том же C++ намного больше сложных конструкций (типа забодробительных variadic templates), которых в принципе нет в rust. И это делает язык сложнее rust. О чём я и писал в начале.

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

Другое дело, что предпочитаю когда (хотя бы) стандартная библиотека не навязывает их использование.

Тут мы, к сожалению, приходим к вопросу что считать «исключительной ситуацией», а что не считать. Например, когда new не может выделить память, мне было бы удобнее получить bad_alloc, а не nullptr с соответствующим значением errno. А вот некий гипотетический «правильный» fopen лучше бы возвращал Result<FILE*, int>, а не бросал исключение.

Но это уже вкусовщина, к сожалению.

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

Так что покажите надежность кодов возврата в C++ по отношению к исключениям в C++. Например, аналог вашего ржавого кода, но на C++.

Открытие файла может возвращать, например, optional - так уже ошибку не проигнорируешь. Можно возвращать и информацию об ошибке - через variant, так что всё возможно.

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

Тем не менее, я не испытываю проблем при написании rust кода.

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

которых в принципе нет в rust

В расте есть другие. Но мы ходим по кругу. (:

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

Например, когда new не может выделить память, мне было бы удобнее получить bad_alloc, а не nullptr с соответствующим значением errno.

Это да, явное протаскивание bad_alloc превратило бы код в ад.

Но другие примеры мне в голову так сразу не приходят.

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

Открытие файла может возвращать, например, optional - так уже ошибку не проигнорируешь.

Да ладно:

optional<FILE *> f = open_file("some_name.txt", "rb");
fread(buf, 1, buf_size, *f);
И всех делов, ничуть не лучше возврата просто нулевого FILE*.

Другое дело, если optional::operator*() делает проверку и бросает исключения. Но это как раз в очередной раз подчеркивает достоинства исключений в C++.

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

Но другие примеры мне в голову так сразу не приходят.

Да ладно. Берем filesystem из C++17, например, directory_iterator и Co:

#include <fstream>
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
 
int main()
{
    fs::create_directories("sandbox/a/b");
    std::ofstream("sandbox/file1.txt");
    std::ofstream("sandbox/file2.txt");
    for(auto& p: fs::directory_iterator("sandbox"))
        std::cout << p << '\n';
    fs::remove_all("sandbox");
}
И пробуем переписать все это без исключений, на кодах возврата. Насколько будет проще/лаконичнее/удобнее/надежнее?

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

Самый простой вариант:

#include <iostream>
#include <experimental/optional>

using namespace std;
using namespace std::experimental;

template<class T>
optional<T> operator+(optional<T> a, optional<T> b) {
    if (!a || !b)
        return {};
    return a.value() + b.value();
}

template<class T>
ostream& operator<<(ostream& os,const optional<T> a) {
    if (a)
        os << a.value();
    else
        os << "<error>";
    return os;
}

int main(int argc, const char *argv[])
{
    optional<int> a{10};
    optional<int> b{20};
    optional<int> c{};

    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    cout << "a+b = " << (a+b) << endl;
    cout << "a+c = " << (a+c) << endl;

    return 0;
}

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

Т.е. если я захочу добавить к a единичку, я буду вынужден писать a+optional<int>(1).

А если я напишу выражение a+b+c+d+e+g, то сколько проверок в нем будет выполняться? И сколько их будет выполняться, если первая же из них вернет признак наличия ошибки?

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

Но это как раз в очередной раз подчеркивает достоинства исключений в C++.

Давай уточним о чём мы спорим. Если противопоставлять исключениям «возврат значений» в общем случае, то да, в последнем случае тоже нужно что-то для рантайм проверок: это может быть «паника» как в го или расте или «полноценные исключения» в С++. И тут нет принципиальной разницы или надо явно unwrap делать или «неявно» получать исключения.

А вот к вопросу о достоинства - это как раз optional с UB. И разница как раз в этом.

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

то тебя ведь это не убедит?

Какой вопрос, такой и ответ.

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

А вот к вопросу о достоинства - это как раз optional с UB. И разница как раз в этом.

Применительно к C++. Пока в C++ нет стандартных средств заставить пользователя нормально проверять возвращаемые значения. Посему программист может либо проигнорировать код возврата, либо же начать использовать возвращенное значение без проверки. Был бы в C++ паттерн-матчинг, тогда можно было бы на уровне компилятора заставить программиста писать что-то вроде:

match(open_file("some_name.txt", "rb")) {
  case(some<FILE*> f) { ... }
  case(int err) {...}
}
И тогда был бы совсем другой разговор о том, насколько выгодно в C++ предпочитать возвращаемые значения исключениям.

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

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

Если «прямо сейчас» и на С++, то придётся изворачиваться и не факт, что оно того стоит. Я всё-таки не призываю полностью выкидывать исключения из С++, как могло показаться. Как минимум, для этого в языке должны появиться дополнительные механизмы.

Пример могу набросать, но сначала надо договориться «засчитаешь» ли ты, если create_directories будет возвращать объект-ошибку кидающий исключение, если потенциальная ошибка не обработана или прицепишься к тому, что исключение осталось.

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

Как минимум, для этого в языке должны появиться дополнительные механизмы.

Ну а вот я считаю, что пока в языке эти дополнительные механизмы не появились, предлагать использовать в C++ возвращаемые значения вместо исключений в большинстве случаев — это глупость. Исключениям нет места, например, в коде, заточенном под hard real-time, под харкорный системный уровень (где-то в дебрях ядра ОС). Ну или если профайлер показал, что исключения просаживают производительность.

Пример могу набросать, но сначала надо договориться «засчитаешь» ли ты, если create_directories будет возвращать объект-ошибку кидающий исключение, если потенциальная ошибка не обработана или прицепишься к тому, что исключение осталось.

Не распарсил.

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

А чему там паниковать

Ну вот дергается там простой unwrap. Разве он не паникует, если внутри лежит Error, а не нужное пользователю значение?

Речь о таком примере:

for entry in WalkDir::new("foo").follow_links(true) {
    let entry = entry.unwrap();
    println!("{}", entry.path().display());
}

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

И тогда был бы совсем другой разговор о том, насколько выгодно в C++ предпочитать возвращаемые значения исключениям.

Я это понимаю, но несмотря на название темы, в обсуждении всплывал и раст и прочее. Так что можно мои комментарии рассматривать как «что могло бы быть», а не «срочно прекращаем использовать исключения (в С++)».

Ну и у нас используется этакий недо-матч, который умеет работать с boost::variant и иерархиями, получается довольно удобно:

match(foo,
    [&](Bar&)
    { ... },
    [&](Baz&)
    { ... });

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

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

Насчёт большинства случаев не согласен, но речи о «полном отказе от исключений» (в рамках С++) не было. По крайней мере, с моей стороны.

Не распарсил.

Я о том, что при использовании «возврата значений вместо исключений» полностью отказаться от последних всё равно нельзя (да и не нужно).

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

Разве он не паникует, если внутри лежит Error, а не нужное пользователю значение?

Конечно, паникует.

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

Т.е. если я захочу добавить к a единичку, я буду вынужден писать a+optional<int>(1).

Я так подозреваю, что у вас там тоже не просто числа были.

А если я напишу выражение a+b+c+d+e+g, то сколько проверок в нем будет выполняться? И сколько их будет выполняться, если первая же из них вернет признак наличия ошибки?

А вот тут да, косяк.

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

Т.е. если я захочу добавить к a единичку, я буду вынужден писать a+optional<int>(1).

Можно перегрузить operator+ и для этого случая.

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

А если я напишу выражение a+b+c+d+e+g, то сколько проверок в нем будет выполняться? И сколько их будет выполняться, если первая же из них вернет признак наличия ошибки?

Зависит от продвинутости компилятора. clang, например, смог выкинуть лишний код, а у gcc совсем грустный вариант получился.

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

Дак это же просто пример. Никто так писать не будет.

Можно изобразить:

for entry in WalkDir::new("foo").follow_links(true) {
    match entry {
        Ok(v) => println!("{}", entry.path().display()),
        Err(e) => {
            println!("{:?}", e);
            break;
        }
    }
}
или
for entry in WalkDir::new("foo").follow_links(true) {
    println!("{}", entry?.path().display());
}

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

Я о том, что при использовании «возврата значений вместо исключений» полностью отказаться от последних всё равно нельзя (да и не нужно).

Именно, о том и речь.

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

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

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

Как минимум, оно будет работать, а твое говно даже не компилируется.

$ g++ -std=gnu++1z -Wall fs.cpp 
fs.cpp:3:22: fatal error: filesystem: No such file or directory
compilation terminated.
$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang++ -std=gnu++1z -Wall fs.cpp 
fs.cpp:3:10: fatal error: 'filesystem' file not found
#include <filesystem>
         ^
1 error generated.
$ clang --version
clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Но мне нравится твой подход: абы какое сообщение об ошибке + некомпилирующийся исходник.

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

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

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

В тестовой проге - почему бы и нет. В либе - сомневаюсь.

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

От компилятора. '?' вызовет return Err(e) при ошибке, и функция, в которой это происходит, должна уметь возвращать этот тип.

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

Ну вот у вас сниппет:

for entry in WalkDir::new("foo").follow_links(true) {
    println!("{}", entry?.path().display());
}
Так какой тип ошибки может выскочить в случае entry??

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

Это не полный код. Полный такой:

fn func() -> Result<(), io::Error> {
    for entry in WalkDir::new("foo").follow_links(true) {
        println!("{}", entry?.path().display());
    }
    Ok(())
}

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

Дебилушка, там ссылка была на первоисточник. Пример соответствует тому, что будет в C++17, после его стандартизации. Естественно, что в настоящий момент gcc-5.4 и clang-3.8 не обязаны поддерживать именно std::filesystem. В gcc-6.3 и clang-3.9 есть std::experimental::filesystem и <experimental/filesystem>. Но это же в теме разбираться нужно. Куда проще на форуме насрать.

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