Pull to refresh

Comments 23

UFO just landed and posted this here

Разве тут написали что не нужен? Вроде подразумевают по контексту, что "если много использовать, то это плохо".

Однако дальше показано, что ещё лучше использовать умные указатели. Получается, что goto опять-таки не нужен.

Ага, написали, что не нужен.

А можно пару-тройку примеров оправданного использования goto в C++ном коде (именно C++ном, а не чисто Сишном)? За исключением автоматически сгенерированных конечных автоматов.

Ну, выскочить из глубоких циклов на С++ тоже удобно, особенно для встраиваемых систем на микроконтроллерах, где всякие рекомендуемые "throw… catch…" редко используются в принципе.

throw+catch для выхода из вложенных циклов -- это дорого даже если исключения разрешены.
Т.е. данный пример относится к категории оптимизации, когда нужно такты сэкономить и не хочется вводить флаги для циклов, а так же у нас не получается (реально?) вынести эти самые глубокие циклы в отдельные функции (где можно делать return).

Какие еще примеры?

Про "throw+catch - дорого" - знаю, я так не делаю, но иногда именно для С++ пишут как рекомендацию, так заменять goto выход из циклов без введения флагов.

У меня нет больше примеров, я больше нигде не использую. Могу ещё придумать гипотетический случай: перенос программы с ASM в С/С++ без переделки алгоритма, "в лоб". Но это сильно экзотика.

У меня нет больше примеров, я больше нигде не использую

Ну вот в том-то и дело, что именно в C++ место для goto осталось разве что на поприще оптимизации, когда правильно работающий код уже есть, но нужно заставить его работать еще быстрее. А по сути-то "goto не нужен". Странно, что кому-то это "религией" кажется.

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

int  Ret_Code;

if (проверка1)
{
    return Error1;
}

действие1;

if (проверка2)
{
    Ret_Code = Error2;
    goto Err1;
}

действие2;

if (проверка3)
{
    Ret_Code = Error3;
    goto Err2;
}

....

    return Success;

ErrN:
    откатN;

...

Err2:
    откат2;

Err1:
    откат1;
    return Ret_Code;

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

Вы это делаете в C++? Не в теплой ламповой Сишечке, а именно в C++?

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

И RAII там тоже ни в каком виде?

Требует использования классов, динамического выделения-освобождения памяти и всё такое. Когда ООП само по себе уместно -- скажем, нужно наследование с полиморфизмом, которые реализовывать средствами чистого Си глупо, если есть Си++, -- это нормально, но в простом низкоуровневом коде... Это чисто его раздувание без какой-либо нужды, да и читабельность снижает: разрывает фактически единую функцию между конструктором и деструктором, а последний заставляет ещё и анализировать условия завершения, чтобы определить, что именно откатывать, а что -- нет. Ну или делать кучу вспомогательных классов с той же целью. В общем, громоздко, нечитаемо и неэффективно. Ну а конструкции типа try-finally в языке нет.

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

Да блин, откуда вы все такие беретесь? Пардон май френч.
Попробуйте сосчитать количество аллокаций, например, здесь

Динамическое выделение/освбождение памяти -- это одна из возможных проблем, а не единственная проблема. Использование "левых" классов "размазывает" программу и сильно усложняет её восприятие -- и ради чего? Только чтоб goto не было? Это уже фанатизм, и приводит он к прямо противоположным результатам (ухудшению читабельности и лёгкости сопровождения кода вместо улучшения).

Если вы в подходе с RAII умудрились увидеть динамическое выделение памяти (как одну из проблем), то я вынужден ваши суждения отправлять прямиком в /dev/null.

Код с goto хрупок и его надежность базируется только на скрупулезности и внимательности программиста. А этим факторам доверять нельзя от слова совсем.

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

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

"Отучаемся говорить за всех" (с)

но надёжность кода всегда базируется именно на них

Нет, не всегда.

Ну и где вы видите размазывание вот в таком коде?


bool success = false;

if (проверка1) return Error1;
действие1;
auto fin1 = finally([&]() {
    if (!success) {
        откат1;
    }
});

if (проверка2) return Error2;
действие2;
auto fin2 = finally([&]() {
    if (!success) {
        откат2;
    }
});

if (проверка3) return Error3;
действие3;
auto fin3 = finally([&]() {
    if (!success) {
        откат3;
    }
});

success = true;
return Success;

Как по мне — от одной только группировки действий и откатов уже стало проще воспринимать код. И это ещё я даже не начинал ничего упрощать...

Здесь размазывания уже нет. Правда, некоторое разбухание исходника (объявление переменных finN, тела лямбд с if'ами и обрамляющие сие дело скобки), но в разумных пределах и вполне читабельное. А можете упростить? Интересно посмотреть, что получится.

Из недостатков -- выделение дополнительной памяти. Да, знаю, не динамической, а в стеке -- но, тем не менее. Когда у тебя всего ОЗУ, скажем, 6 Кбайт на всё про всё, это может быть критичным (а может и не быть -- от задачи зависит). Плюс, некоторое снижением производительности, но это даже на слабых МК крайне редко проблемой бывает (а где бывает, лично я бы просто перешёл на ассемблер -- на нём я всяко напишу и более быстрый, и более компактный код, чем самый наилучший компилятор).

В Arduino и 2 кБ памяти бывает. Хотя там уже есть и лямбды, и прочие фишки вплоть до C++17.

Да можно и C++20, ежели компилятор собрать самому или найти уже готовый современный. Там же обычный GCC.

Sign up to leave a comment.