LINUX.ORG.RU

exceptions в C++

 ,


5

9

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

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

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

★★★★★

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

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

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

Дык, найдётся куча народу, которые предпочтут ловить расстрелы памяти и утечки из-за того, что «на расте двусвязный список сложно написать!».

Если мы вычтем всё общее между rust и c++, то у c++ останется вагон и маленькая тележка фич, а у rust: лайфтаймы, PM, ADT и трейты. Я об этом.

Сильно зависит от того как считать.

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

Этот фрагмент даже с Modern C++ Design не сравнить, куда там Folly.

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

В последнее время, точка зрения поменялась. Сомневаюсь, что тут можно привести какие-то железные аргументы, ведь всегда их можно свести к «вы просто не умеете их готовить».

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

stream << length << payload << checksum;
Или вот так:
data_source
  | calibration | validation
  | fork( (src | store_statsd),
          (src | value_checker | alarm_detector | alarm))

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

Как-то так:

obj.fallible_op().and_then(|| another_fallible_op()).and_then(|| yet_another_fallible_op())

Не сильно отличается от варианта с исключениями, на самом деле. Трюк в том, что все операции возвращают Result, а у него есть метод and_then: https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then

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

Ты запиши сначала показанный код. Еще лучше, что-нибудь арифметическое. А то в реально замороченном коде все эти ваши and_then превращаются в такие же простыни, как и if err в Go.

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

Ты запиши сначала показанный код

Показанный код делает что-то сложное - мне тупо влом выяснять, что именно, и писать аналог. Цепочка вычислений будет выглядеть примерно так, как я показал, и ничем принципиальным от Си++ не отличается.

Еще лучше, что-нибудь арифметическое

fn main() {
    let s = String::from("123");
    let i = s.parse::<u32>()
             .and_then(|v| Ok(v+1))
             .and_then(|v| Ok(v*2))
             .and_then(|v| Ok(v*v))
             .unwrap();
    println!("{}", i);
}

А то в реально замороченном коде все эти ваши and_then превращаются в такие же простыни, как и if err в Go.

В такие же - не превращаются. Превращаются в одну длинную макаронину, но я полагаю, с этим можно бороться путем перегрузки операторов.

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

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

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

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

Ну т.е. вместо sqr((parse("123") + 1)*2) записывать последовательность из and_then — это нормально. Okay.

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

А каждый перегруженный оператор будет иметь вид:

fn opPlus(a: Result<i32,Err>, b: i32) {
  match(a) {
    Some(i) => i + b;
    Err(e) => e
  }
}
?

Или, что еще хуже, нужно ожидать двух аргументов типа Result<T, Err> и проверять валидность каждого.

Прелестно.

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

Так это перегрузка |?

Да, всего лишь.

Зачем так сложно?

ЕМНИП, компиляторы ругаются на высоких уровнях предупреждений о сравнении uint8 и int. Беззнаковое со знаковым, да еще разных размерностей, что не есть хорошо.

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

Дык современные компиляторы ругаются на сишный каст, если мне не отшибает память. Особенно с сочетаниями -std=c++14 и -Weverything (-Wall).

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

вместо sqr((parse(«123») + 1)*2) записывать последовательность из and_then — это нормально. Okay.

Не понял придирки. Я не предлагаю вычислять арифметические выражения цепочками and_then, это просто пример - ты хотел арифметики, вот арифметика.

нужно ожидать двух аргументов типа Result<T, Err> и проверять валидность каждого.

Да.

что еще хуже

Кому как. Мне хуже ожидать вылета исключения в каждом операторе.

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

Что сложного в:

// да, sqrt для int нету
fn sqrt(n: u32) -> u32 {
    (n as f64).sqrt() as u32
}

fn myfunc() -> Result<u32, std::num::ParseIntError> {
    Ok(sqrt(("123".parse::<u32>()? + 1) * 2))
}

Тут хотя бы сразу видно кто кидает ошибки.

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

Это вообще с++?

вот такие невменяемые господа, не видевшие плюсов, кодят на расте)

впринципе школоло и раньше хипстерила на разных недоязыках, но раст - это какой-то полный парад мракобесия)

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

вот такие невменяемые господа, не видевшие плюсов, кодят на расте)

Или они видели побольше языков, чем ты. Например, приведенный фрагмент может быть валидным Python-кодом, не говоря о какой-нибудь экзотике.

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

Или они видели побольше языков, чем ты. Например, приведенный фрагмент может быть валидным Python-кодом, не говоря о какой-нибудь экзотике.

скорее и вы и они - демагоги, сознательно не замечающие контекста сообщения

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

Я понял контекст сообщения, но я понимаю и то, почему кто-то решил уточнить.

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

Я не предлагаю вычислять арифметические выражения цепочками and_then, это просто пример - ты хотел арифметики, вот арифметика.

Т.е. ты привел пример того, как делать не нужно?

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

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

Вот мы и пришли к тому, что тебе лучше писать портянки, как в Go, только чутка по другому оформленные. Но при этом еще и с наличием паник, которые могут вылететь откуда-нибудь.

Все это гораздо лучше исключений, так и запишем.

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

fn myfunc() -> Result<u32, std::num::ParseIntError> {
Ok(sqrt((«123».parse::<u32>()? + 1) * 2))
}

Потом мы захотим поменять u32 на какой-нибудь constrained_int<int32, -4095, 4096> и у нас внезапно окажется, что функция myfunc начнет возвращать Result не только с ParseIntError, но и с другими вариантами ошибок.

Еще веселее, если myfunc изначально была шаблонной и ей было фиолетово, подсовывают ли ей u32, int64 или constrained_int<int32, -4095, 4096>.

Собственно, пусть в Rust-е используют то, что Rust-оманам кажется удобным. Но вот в C++ есть места, которые без исключений будут тупо очень неудобны. И странно видеть точку зрения, что в таких местах коды возврата (пусть даже в виде Result-ов или Expected или еще каких-то заимствований из языков с АлгТД) удобнее исключений.

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

Т.е. ты привел пример того, как делать не нужно?

Нет.

Вот мы и пришли к тому, что тебе лучше писать портянки, как в Go, только чутка по другому оформленные

Не мы, а вы.

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

Нет.

Тогда приведи пример того, как бы ты делал. В C++ (ибо Rust-ом все равно нихрена не пользуешься и не будешь пользоваться).

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

Тогда приведи пример того, как бы ты делал

Я привел. Но ты на своей волне.

В C++ (ибо Rust-ом все равно нихрена не пользуешься и не будешь пользоваться).

Си++ позволяет сделать то же, что и Rust.

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

Я привел.

Если ты про пример отсюда: exceptions в C++ (комментарий) то, очевидно, твое мнение о том, как писать вычислительный код на C++ можно смело игнорировать.

Си++ позволяет сделать то же, что и Rust.

В C++ нет АлгТД, нет ПМ, нет макросов try! и конструкции ? из Rust 1.14. Так что выключи дурочку и попробуй начать общаться нормально. Если лень, то так и скажи.

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

но и с другими вариантами ошибок.

И какие проблемы? Есть трейт From.

И странно видеть точку зрения, что в таких местах коды возврата

Я знаю только одно такое место - итераторы.

У исключений своих проблем хватает: не известно какое исключение выкинет функция, можно работать только с синхронным кодом. Как только начинается асинхронность в любом виде - прощайте исключения.

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

И какие проблемы?

В сигнатуре функции: Result<u32, std::num::ParseIntError> Здесь уже зашит единственный тип возможной ошибки.

не известно какое исключение выкинет функция,

Как по мне, так в большинстве случаев это не имеет значения.

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

С чего бы?

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

Си++ позволяет сделать то же, что и Rust.

В C++ нет АлгТД

У меня есть вполне достаточная имитация.

нет ПМ

Это печально, но не критично.

нет макросов try! и конструкции ?

Я их и не использовал.

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

Просто дальше тратить силы бессмысленно. Технически ты уже всё понял и начались придирки и вкусовщина.

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

Технически ты уже всё понял и начались придирки и вкусовщина.

Я вот не могу понять, как человек с твоим опытом может всерьез предлагать цепочку из and_then на замену нормальным арифметическим выражениям. Единственное объяснение: тебе никогда плотно с вычислениями сталкиваться не приходилось, ты больше системщиной занимался.

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

Здесь уже зашит единственный тип возможной ошибки.

Ну так это пример. Ни кто не мешает написать:

enum Error {
    ParseError(std::num::ParseIntError),
    // любые другие типы ошибок
}

impl From<std::num::ParseIntError> for Error {
    fn from(err: std::num::ParseIntError) -> Error {
        Error::ParseError(err)
    }
}

fn myfunc() -> Result<u32, Error> {
...
}

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

Как по мне

Аргументированно.

С чего бы?

Ну я по прежнему смотрю с колокольни Qt, там исключения с многопоточностью и сигналами/слотами не дружат от слова совсем.

Пример:

static void test()
{
    throw "qwe";
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    try {
        QtConcurrent::run(test);
    } catch (...) {
        qDebug() << "lol";
    }

    return a.exec();
}

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

всерьез предлагать

Сказал человек, использующий лапшу из a | b | c | d. Как это писать, понимать и сопровождать - ума не приложу.

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

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

Для написания обобщенного кода — это боль. Например, в Scala (и в последующих современных языках для JVM) отказались от использования checked exceptions именно потому, что это мешало написанию обобщенного кода (тех же обобщенных версий map, fold и пр.). В каком-то смысле Java-вские checked exceptions дают такой же перечень возможных ошибок в коде, как и в enum-ы для ошибок в Rust-е.

Аргументированно.

Ну вот такой у меня опыт. Он подсказывает, что в большинстве случаев нужно получить результат z = a*x + b*y, а если это не удается, то не нужно анализировать причин неудачи и пытаться что-то подправить. Исключение вылетает, исполнение прерывается, ошибка не игнорируется. А что уже там за исключение — не суть важно.

Ну я по прежнему смотрю с колокольни Qt, там исключения с многопоточностью и сигналами/слотами не дружат от слова совсем.

Qt появилась во времена, когда C++ные исключения далеко не все компиляторы поддерживали. Что вовсе не означает, что исключения и асинхронность несовместимы в принципе. Можно на другие языки посмотреть, вроде JS, Ruby или Python-а. Одно другому не мешает.

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

Сказал человек, использующий лапшу из a | b | c | d. Как это писать, понимать и сопровождать - ума не приложу.

Да ладно, посмотрите на какие-нибудь Rx-ы (RxJava или RxCpp). Вполне себе работающее DSL-естроения для dataflow programming-а.

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

Так при чём тут Qt. Это просто пример. Можете через std::thread запускать метод - результат будет тот же.

Можно на другие языки посмотреть, вроде JS, Ruby или Python-а.

Шта? У JS и Python вообще нет многопоточности.

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

Я знаю о реактивном программировании, но я не считаю его адекватным.

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

Можете через std::thread запускать метод - результат будет тот же.

Смотря как запускать и как делать взаимодействие между нитями. Те же std::promise и std::future отлично могут передавать исключения.

Шта? У JS и Python вообще нет многопоточности.

Так мы про асинхронность или про многопоточность говорим? Асинхронность вполне себе живет на одном потоке.

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

QFuture тоже может, но костыльно и с копированием.

Как работае асинхронность в js/python не знаю, может там это как-то решили. Решения для C++ не знаю. В том же Qt чётко сказано, что сигналы-слоты и исключения не совместимы.

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

предлагать цепочку из and_then на замену нормальным арифметическим выражениям

Первые абзацы здесь и здесь. Ты показал, как выглядит цепочка вычислений в Си++ с исключениями - я показал, как она выглядит в Rust без исключений. Rust использован только потому, что в нем есть готовый Result с документацией, но всё то же можно написать и на Си++.

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

GIL - деталь реализации, которая упрощает VM, но не прикладные программы. Близкий аналог GIL - выполнение многонитевой программы на одном процессоре.

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

Я же говорю - из-за GIL нити в пределах одной VM фактически пускаются на одном процессоре. Если нужно параллелить CPU-bound код, единственный выход - использовать несколько VM, а самый простой способ для этого - несколько процессов.

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

понимать и сопровождать - ума не приложу.

с вами всем все понятно давно ;) можно уже не выпячивать свою глупость :-)

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