Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
Здравствуйте, GhostCoders, Вы писали:
GC>Добрый день!
GC>Думаю, что следующий подход в корне неверен:
GC>...
GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
Нормальный подход, чем плохо то? Лучше чем дублирование кода. Я бы конечно предпочел RAII использовать.
Тут весь код с запашком: передача shared_ptr везде по значению, где это судя по всему этого вообще не требуется, захват в замыкание опять-таки по значению. Принудительная очистка на исключениях намекает на то что и вовсе shared_ptr не нужен ибо судя по всему нет разделения этих данных с кем-то ещё.
GC>
GC> // ...
GC> int error_state = some_function3();
GC> if (!error_state)
GC>
GC>Думаю, что следующий подход в корне неверен
Нужно больше контекста. Судя по тому что код ошибки конвертируется в исключение это интеграция с каким-то сторонним API — и в таком контексте может быть вполне оправданно (как уже выше сказали — это лучше чем копипаста).
Если же говорить о подходе в общем, для обычного кода, то естественно лучше использовать RAII, а не разбрасывать по всему коду ручные лямбды с try/catch'ами.
Если же говорить про сабж в изоляции от приведённого кода — то например ScopeGuard + замыкание это вполне себе общепринятая идиома
GC>Фактически здесь лямбда с захватом контекста используется как макрос
Какой макрос?
GC>то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
Повторюсь, если речь идёт про обычный код, то действительно так делать не нужно. Если же это интеграция с каким-то левым API, то нужно смотреть по контексту.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
GC>>Думаю, что следующий подход в корне неверен
EP>Нужно больше контекста. Судя по тому что код ошибки конвертируется в исключение это интеграция с каким-то сторонним API — и в таком контексте может быть вполне оправданно (как уже выше сказали — это лучше чем копипаста).
Так и есть — сторонний API.
Конвертация кодов ошибок стороннего API в "наши" исключения,
конвертация типов исключений стороннего API в "наши" исключения.
EP>Если же говорить о подходе в общем, для обычного кода, то естественно лучше использовать RAII, а не разбрасывать по всему коду ручные лямбды с try/catch'ами.
Угу
GC>>Фактически здесь лямбда с захватом контекста используется как макрос EP>Какой макрос?
Как макрос. То есть грубо говоря лябда здесь выполняет роль вот такого макроса (или может быть заменена на его):
#define deleter image->Clear(); color_data->Clear();
// ну или для пенантов вот так:#define deleter image->Clear(); color_data->Clear
// так как используется как deleter();
Конечно, макросы это Адъ и Израиль, но вот использовать лямбды в таком качестве (читай в качестве макросов) — это все равно что микроскопом гвозди забивать.
GC>>то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно. EP>Повторюсь, если речь идёт про обычный код, то действительно так делать не нужно. Если же это интеграция с каким-то левым API, то нужно смотреть по контексту.
Интеграция со сторонним API, но RAII все равно использовать предпочтительней.
А трансляция исключений, кодов ошибок в "наши" исключения в таком случае все равно останутся.
Здравствуйте, GhostCoders, Вы писали:
GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
Плохо здесь не то, что лямбда с захватом контекста напоминает вам макрос (на самом деле здесь лямбда работает как локальные функции, существующие в некоторых других языках). А то, что непонятно, этот deleter должен вызываться при любом возврате из function1 или нет. Если при любом, то лучше было бы имитировать D-щные scope(exit), например, вот так:
Здравствуйте, so5team, Вы писали:
S>Если же deleter должен отработать только в случае выбрасывания исключения, то можно было бы использовать небольшой готовый велосипед: S>Либо же изобразить его самостоятельно: S>
template<typename L>
S>class transaction {
S>...
Спасибо!
Чем плох вот такой вариант:
template<typename L>
class transaction {
L l_;
public :
transaction(L && l) : l_{std::move(l)} {}
~transaction() {
if(std::uncaught_exception()) l_();
}
};
template<typename L> auto make_trx(L && l) { return transaction<L>{std::forward<L>(l)}; }
Т.е. метод commit() не используется, вместо него std::uncaught_exception() как индикатор выброшенного исключения?
Здравствуйте, GhostCoders, Вы писали:
GC>Т.е. метод commit() не используется, вместо него std::uncaught_exception() как индикатор выброшенного исключения?
std::uncaught_exception недостаточно, так как есть случаи когда он возвращает true, но в текущем scope не было исключения.
В том числе по этой причине я и делал stack_unwinding
, которая внутри реализует uncaught_exception_count. Именно по этой причине в C++17 и ввели эту функцию с названием std::uncaught_exceptions.
У Александреску есть несколько выступлений на эту тему, в том числе вот
Здравствуйте, Evgeny.Panasyuk, Вы писали:
GC>>Т.е. метод commit() не используется, вместо него std::uncaught_exception() как индикатор выброшенного исключения?
И насколько часто?
EP>std::uncaught_exception недостаточно, так как есть случаи когда он возвращает true, но в текущем scope не было исключения. EP>В том числе по этой причине я и делал stack_unwinding
, которая внутри реализует uncaught_exception_count. Именно по этой причине в C++17 и ввели эту функцию с названием std::uncaught_exceptions. EP>У Александреску есть несколько выступлений на эту тему, в том числе вот
Здравствуйте, GhostCoders, Вы писали:
EP>>Нужно больше контекста. Судя по тому что код ошибки конвертируется в исключение это интеграция с каким-то сторонним API — и в таком контексте может быть вполне оправданно (как уже выше сказали — это лучше чем копипаста). GC>Так и есть — сторонний API.
Мало контекста. Для полноты картины можешь привести вариант без лямбд — тогда и будет видно какой вариант лучше.
GC>>>Фактически здесь лямбда с захватом контекста используется как макрос EP>>Какой макрос? GC>Как макрос. То есть грубо говоря лябда здесь выполняет роль вот такого макроса (или может быть заменена на его): GC>
GC> #define deleter image->Clear(); color_data->Clear();
GC> // ну или для пенантов вот так:
GC> #define deleter image->Clear(); color_data->Clear
GC> // так как используется как deleter();
GC>
GC>Конечно, макросы это Адъ и Израиль, но вот использовать лямбды в таком качестве (читай в качестве макросов) — это все равно что микроскопом гвозди забивать.
Это как-то странно. Мол если что-то реализуемо макросами, значит если это же реализуется лямбдами, то лямбды в таком качестве использовать не стоит.
С тем же успехом можно сказать что не стоит использовать const double pi, так как это же реализуется макросом #define PI, и значит такое использование констант в качестве макросов плохо
Макросы решают вполне конкретные задачи, и сами по себе задачи не являются плохими. Плохо именно то что в решениях через макросы есть много недостатков, а не то что это задачи какие-то неправильные.
Вот например есть макрос BOOST_FOREACH, в C++11 появилась решение встроенное в язык решающее ровно ту же задачу — range-based for. То что раньше эта задача решалась через BOOST_FOREACH — не делает саму задачу плохой, а range-based for ненужным.
Касательно же использования замыканий для сокращения копипасты внутри области одной функции (без передачи замыкания в другую) — в этом нет ничего плохого (если конечно нет более лучшего решения). Лямбды не являются каким-то ритуальным микроскопом, а как раз таки наоборот предназначены для ad-hoc забивания гвоздей.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Это как-то странно. Мол если что-то реализуемо макросами, значит если это же реализуется лямбдами, то лямбды в таком качестве использовать не стоит. EP>С тем же успехом можно сказать что не стоит использовать const double pi, так как это же реализуется макросом #define PI, и значит такое использование констант в качестве макросов плохо
Тут даже не про макросы речь идет.
Явный вызов deleter(); — вот это осноная проблема. Это примерно тоже самое как раньше делали
goto cleanup;
который нужно руками вызывать в нужных местах.
А лямбда тут лишь позволяет "мехнически" убрать копи-пасту, примерно как макрос или локальная функция.
Здравствуйте, GhostCoders, Вы писали:
GC>Как-то жутковато смотреть на реализацию stack_unwinding.
Так в самом языке это было очевидно не реализовать, поэтому и пришлось использовать платформенно-специфичные трюки, которые я выкопал "грубой силой" смотря на std::uncaught_exception.
Facebook потом чуть-чуть облагородил в своей библиотеке, но суть та же.
GC>C std::uncaught_exceptions, надеюсь проблем нет?
Каких конкретно проблем? В конкретной реализации std::uncaught_exceptions под капотом там также будет обращение к платформенно-специфичным структурам (примеры: 1, 2)
Здравствуйте, GhostCoders, Вы писали:
EP>>Это как-то странно. Мол если что-то реализуемо макросами, значит если это же реализуется лямбдами, то лямбды в таком качестве использовать не стоит. EP>>С тем же успехом можно сказать что не стоит использовать const double pi, так как это же реализуется макросом #define PI, и значит такое использование констант в качестве макросов плохо GC>Тут даже не про макросы речь идет. GC>Явный вызов deleter(); — вот это осноная проблема. Это примерно тоже самое как раньше делали GC>
GC>goto cleanup;
GC>
GC>который нужно руками вызывать в нужных местах. GC>А лямбда тут лишь позволяет "мехнически" убрать копи-пасту, примерно как макрос или локальная функция.
Так это же наоборот хорошо что она помогает убирать копипасту.
В общем как выяснилось претензия вовсе не к использованию лямбд для очистки, а к явному вызову deleter() (неважно каким образом он реализован — через лямбду или как-то ещё).
В приведённом примере мало контекста — непонятно зачем вообще очищать, почему используются выходные параметры, а не return, и т.д. и т.п. — зная весь контекст ты можешь привести альтернативную реализацию, тогда и можно сделать сравнение вариантов.
Как уже отмечалось выше, альтернативой могут быть например ScopeGuard, scope(exit/failure/success) — причём обычно они используются именно с лямбдами — но насколько они подходят под задачу до конца не ясно — может там и вовсе достаточно рефакторинга и обычного RAII.
Здравствуйте, GhostCoders, Вы писали:
GC>Добрый день!
GC>Думаю, что следующий подход в корне неверен: GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
IMHO, все в порядке. У этого подхода врожденных проблем макросов нет, так что это не макрос.
Вообще что есть лямбда, и чего с ней носиться как с писаной торбой? Это же почти то же самое, что и локальная функция. Поскольку в плюсах их нет, приходится выкручиваться. Если ты сделаешь "честный" RAII с классом, код получится больше. Я бы возможно еще шаг сделал и вместо "deleter"-а — "executor":
Здравствуйте, bnk, Вы писали:
bnk>Если ты сделаешь "честный" RAII с классом, код получится больше.
Это как раз не проблема — если получится декомпозировать запутанную логику на отдельные компоненты, пусть и суммарно немного большим объёмом кода — то часто оно того стоит, даже если эти компоненты далее не будут повторно использоваться, IMO. А если они ещё и будут повторно использованы, то и вовсе нет никаких сомнений.
Тут вопрос в том что именно требуется в этой задаче, и в какой мере применим "честный" RAII.
bnk>Я бы возможно еще шаг сделал и вместо "deleter"-а — "executor":
Там по коду разные варианты — в одном пере-throw, в другом конвертация error code в исключение.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Это как раз не проблема — если получится декомпозировать запутанную логику на отдельные компоненты, пусть и суммарно немного большим объёмом кода — то часто оно того стоит, даже если эти компоненты далее не будут повторно использоваться, IMO. А если они ещё и будут повторно использованы, то и вовсе нет никаких сомнений.
Ну да, что делают программисты, когда собираются вместе? Правильно, начинают писать фреймворк (c)
EP>Там по коду разные варианты — в одном пере-throw, в другом конвертация error code в исключение.
Ну это в принципе обходится:
auto executor = [&](auto action)
{
try
{
if (!action())
throw 0; // <--- ахахаа
}
catch (...)
{
image->Clear();
color_data->Clear();
throw some_exception("Error text...");
}
};
executor([]() { some_function2(); return true; }); // <-- больше лямбд, хороших и разных
executor(&some_function3);
Здравствуйте, so5team, Вы писали:
K>>Функцию полезной нагрузки забыл в шаблон передать по анлогии с делитером S>Не, не забыл. Встречался с тем, что для некоторых людей запись: S>
auto trx = make_trx([&]{rollback_actions();});
S>do_something_useful();
S>trx.commit();
S>