Использование лямбд для очистки
От: GhostCoders Россия  
Дата: 03.02.17 07:57
Оценка:
Добрый день!

Думаю, что следующий подход в корне неверен:

void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
    auto deleter = [image, color_data]() {image->Clear(); color_data->Clear(); };
    
    // ...
    try
    {
        some_function2();
    }
    catch (...)
    {
        deleter();
        throw some_exception("Error text...");
    }

    // ...
    int error_state = some_function3();
    if (!error_state)
    {
        deleter();
        throw some_exception("Error text...");
    }
    // ...
}


Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.
Третий Рим должен пасть!
Re: Использование лямбд для очистки
От: chaotic-kotik  
Дата: 03.02.17 08:21
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Добрый день!


GC>Думаю, что следующий подход в корне неверен:


GC>...


GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.


Нормальный подход, чем плохо то? Лучше чем дублирование кода. Я бы конечно предпочел RAII использовать.
Re: Использование лямбд для очистки
От: kov_serg Россия  
Дата: 03.02.17 08:36
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Добрый день!


GC>Думаю, что следующий подход в корне неверен:


Точно в корне неверен. Нафиг тебе лямбды
void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data) {
    struct Local {
        std::shared_ptr<Image> image;
        std::shared_ptr<ColorData> color_data;
        void clear() { image->Clear(); color_data->Clear(); }
        bool invalid; 
        Local() { invalid=true; }
        ~Local() { if (invalid) { clear(); } }
    } local; 
    local.image=image;
    local.color_data=color_data;
    ....
    try {
        some_function2();
    } catch (...) {
        throw some_exception("Error text...");
    }
    ....
    local.invalid=false;
}
Re: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 03.02.17 08:46
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>
GC>void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
GC>{
GC>    auto deleter = [image, color_data]() {image->Clear(); color_data->Clear(); };
GC>


Тут весь код с запашком: передача 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, то нужно смотреть по контексту.
Re[2]: Использование лямбд для очистки
От: GhostCoders Россия  
Дата: 03.02.17 09:36
Оценка:
Здравствуйте, 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 все равно использовать предпочтительней.
А трансляция исключений, кодов ошибок в "наши" исключения в таком случае все равно останутся.
Третий Рим должен пасть!
Re: Использование лямбд для очистки
От: so5team https://stiffstream.com
Дата: 03.02.17 09:42
Оценка: 13 (3) +2
Здравствуйте, GhostCoders, Вы писали:

GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.


Плохо здесь не то, что лямбда с захватом контекста напоминает вам макрос (на самом деле здесь лямбда работает как локальные функции, существующие в некоторых других языках). А то, что непонятно, этот deleter должен вызываться при любом возврате из function1 или нет. Если при любом, то лучше было бы имитировать D-щные scope(exit), например, вот так:
void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
    auto deleter = cpp_util_3::at_scope_exit([image, color_data]() {image->Clear(); color_data->Clear(); });
    ...
}


Если же deleter должен отработать только в случае выбрасывания исключения, то можно было бы использовать небольшой готовый велосипед:
void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
    cpp_util_3::do_with_rollback_on_exception(
       [&]{ /* основная работа */ },
       [&]{ image->Clear(); color_data->Clear(); } // Очистка при исключении.
    );
}

Либо же изобразить его самостоятельно:
template<typename L>
class transaction {
  L l_;
  bool committed_{false};
public :
  transaction(L && l) : l_{move(l)} {}
  ~transaction() {
    if(!committed) l_();
  }
  void commit() { committed_ = true; }
};
template<typename L> auto make_trx(L && l) { return trx<L>{forward<L>(l)}; }

void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
  auto trx = make_trx([&]{ image->Clear(); color_data->Clear(); });
  ...
  trx.commit();
}
Re[2]: Использование лямбд для очистки
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 03.02.17 10:40
Оценка:
Здравствуйте, so5team, Вы писали:

S>Либо же изобразить его самостоятельно:

S>
template<typename L>
S>class transaction {
S>  L l_;
S>  bool committed_{false};
S>public :
S>  transaction(L && l) : l_{move(l)} {}
S>  ~transaction() {
S>    if(!committed) l_();
S>  }
S>  void commit() { committed_ = true; }
S>};
S>template<typename L> auto make_trx(L && l) { return trx<L>{forward<L>(l)}; }

S>void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
S>{
S>  auto trx = make_trx([&]{ image->Clear(); color_data->Clear(); });
S>  ...
S>  trx.commit();
S>}
S>

Функцию полезной нагрузки забыл в шаблон передать по анлогии с делитером
Sic luceat lux!
Re[3]: Использование лямбд для очистки
От: so5team https://stiffstream.com
Дата: 03.02.17 10:56
Оценка: +1
Здравствуйте, Kernan, Вы писали:

K>Функцию полезной нагрузки забыл в шаблон передать по анлогии с делитером


Не, не забыл. Встречался с тем, что для некоторых людей запись:
auto trx = make_trx([&]{rollback_actions();});
do_something_useful();
trx.commit();

воспринимается гораздо лучше, чем:
do_with_rollback(
  [&]{ do_something_useful(); },
  [&]{ rollback_actions(); });
Re[2]: Использование лямбд для очистки
От: GhostCoders Россия  
Дата: 03.02.17 14:29
Оценка:
Здравствуйте, 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() как индикатор выброшенного исключения?
Третий Рим должен пасть!
Re: Использование лямбд для очистки
От: andrey.desman  
Дата: 03.02.17 15:06
Оценка:
Здравствуйте, GhostCoders, Вы писали:

Лентяи делют так:

void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
    auto deleter = [image, color_data](void*) {image->Clear(); color_data->Clear(); };
    std::unique_ptr<void, decltype(deleter)> mr_proper(reinterpret_cast<void*>(&deleter), deleter);
    // ...
    try
    {
        some_function2();
    }
    catch (...)
    {
        throw some_exception("Error text...");
    }

    // ...
    int error_state = some_function3();
    if (!error_state)
    {
        throw some_exception("Error text...");
    }
    // ...
    mr_proper.release();
}
Re[3]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 03.02.17 15:21
Оценка: 4 (1) +1
Здравствуйте, GhostCoders, Вы писали:

GC>Т.е. метод commit() не используется, вместо него std::uncaught_exception() как индикатор выброшенного исключения?


std::uncaught_exception недостаточно, так как есть случаи когда он возвращает true, но в текущем scope не было исключения.
В том числе по этой причине я и делал stack_unwinding
Автор: Evgeny.Panasyuk
Дата: 27.09.12
, которая внутри реализует uncaught_exception_count. Именно по этой причине в C++17 и ввели эту функцию с названием std::uncaught_exceptions.
У Александреску есть несколько выступлений на эту тему, в том числе вот
Автор: Skorodum
Дата: 06.06.14
( https://vimeo.com/album/2912215/video/97329153 )
Отредактировано 03.02.2017 15:27 Evgeny.Panasyuk . Предыдущая версия .
Re[4]: Использование лямбд для очистки
От: GhostCoders Россия  
Дата: 03.02.17 16:11
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

GC>>Т.е. метод commit() не используется, вместо него std::uncaught_exception() как индикатор выброшенного исключения?

И насколько часто?

EP>std::uncaught_exception недостаточно, так как есть случаи когда он возвращает true, но в текущем scope не было исключения.

EP>В том числе по этой причине я и делал stack_unwinding
Автор: Evgeny.Panasyuk
Дата: 27.09.12
, которая внутри реализует uncaught_exception_count. Именно по этой причине в C++17 и ввели эту функцию с названием std::uncaught_exceptions.

EP>У Александреску есть несколько выступлений на эту тему, в том числе вот
Автор: Skorodum
Дата: 06.06.14
( https://vimeo.com/album/2912215/video/97329153 )


Как-то жутковато смотреть на реализацию stack_unwinding.
C std::uncaught_exceptions, надеюсь проблем нет?
Третий Рим должен пасть!
Re[3]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 03.02.17 16:37
Оценка:
Здравствуйте, 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 забивания гвоздей.
Re[4]: Использование лямбд для очистки
От: GhostCoders Россия  
Дата: 03.02.17 16:46
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

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

EP>С тем же успехом можно сказать что не стоит использовать const double pi, так как это же реализуется макросом #define PI, и значит такое использование констант в качестве макросов плохо
Тут даже не про макросы речь идет.
Явный вызов deleter(); — вот это осноная проблема. Это примерно тоже самое как раньше делали
goto cleanup;

который нужно руками вызывать в нужных местах.
А лямбда тут лишь позволяет "мехнически" убрать копи-пасту, примерно как макрос или локальная функция.
Третий Рим должен пасть!
Re[5]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 04.02.17 10:19
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Как-то жутковато смотреть на реализацию stack_unwinding.


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

GC>C std::uncaught_exceptions, надеюсь проблем нет?


Каких конкретно проблем? В конкретной реализации std::uncaught_exceptions под капотом там также будет обращение к платформенно-специфичным структурам (примеры: 1, 2)
Отредактировано 04.02.2017 10:55 Evgeny.Panasyuk . Предыдущая версия .
Re[5]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 04.02.17 10:41
Оценка: +1
Здравствуйте, 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.
Re: Использование лямбд для очистки
От: bnk СССР http://unmanagedvisio.com/
Дата: 04.02.17 11:15
Оценка:
Здравствуйте, GhostCoders, Вы писали:

GC>Добрый день!


GC>Думаю, что следующий подход в корне неверен:

GC>Фактически здесь лямбда с захватом контекста используется как макрос, то есть когда по-старинке писали вручную очистку ресурсов (до RAII-эпохи), так делать не нужно.

IMHO, все в порядке. У этого подхода врожденных проблем макросов нет, так что это не макрос.
Вообще что есть лямбда, и чего с ней носиться как с писаной торбой? Это же почти то же самое, что и локальная функция. Поскольку в плюсах их нет, приходится выкручиваться. Если ты сделаешь "честный" RAII с классом, код получится больше. Я бы возможно еще шаг сделал и вместо "deleter"-а — "executor":
void function1(std::shared_ptr<Image> image, std::shared_ptr<ColorData> color_data)
{
    auto executor = [&](auto action, auto error_text)
    {
        try
        {
            return action();
        }
        catch (...)
        {
            image->Clear(); 
            color_data->Clear();
            throw some_exception(error_text);
        }
    };
    
    executor(&some_function2, "Error text...");
}
Re[2]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 04.02.17 12:04
Оценка:
Здравствуйте, bnk, Вы писали:

bnk>Если ты сделаешь "честный" RAII с классом, код получится больше.


Это как раз не проблема — если получится декомпозировать запутанную логику на отдельные компоненты, пусть и суммарно немного большим объёмом кода — то часто оно того стоит, даже если эти компоненты далее не будут повторно использоваться, IMO. А если они ещё и будут повторно использованы, то и вовсе нет никаких сомнений.
Тут вопрос в том что именно требуется в этой задаче, и в какой мере применим "честный" RAII.

bnk>Я бы возможно еще шаг сделал и вместо "deleter"-а — "executor":


Там по коду разные варианты — в одном пере-throw, в другом конвертация error code в исключение.
Re[3]: Использование лямбд для очистки
От: bnk СССР http://unmanagedvisio.com/
Дата: 04.02.17 12:37
Оценка:
Здравствуйте, 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);
Re[4]: Использование лямбд для очистки
От: Evgeny.Panasyuk Россия  
Дата: 04.02.17 17:59
Оценка: 8 (1)
Здравствуйте, so5team, Вы писали:

K>>Функцию полезной нагрузки забыл в шаблон передать по анлогии с делитером

S>Не, не забыл. Встречался с тем, что для некоторых людей запись:
S>
auto trx = make_trx([&]{rollback_actions();});
S>do_something_useful();
S>trx.commit();
S>

S>воспринимается гораздо лучше, чем:
S>
do_with_rollback(
S>  [&]{ do_something_useful(); },
S>  [&]{ rollback_actions(); });
S>


Здесь дело не только в восприятии, но и в композиции. Александреску раскрывает эту тему вот в этой презентации (первые 27 минут).

Линейная композиция:
scope(failure)
{
    rollback1();
};
scope(success)
{
    commit1();
};
RAII action1;
/****************/
scope(failure)
{
    rollback2();
};
scope(success)
{
    commit2();
};
RAII action2;
/****************/
scope(failure)
{
    rollback3();
};
scope(success)
{
    commit3();
};
RAII action3;

На try-catch аналогичная композиция будет вложенной:
try
{
    RAII action1;
    try
    {
        RAII action2;
        try
        {
            RAII action3;
        }
        catch(...)
        {
            rollback3();
            throw;
        }
        commit3();
    }
    catch(...)
    {
        rollback2();
        throw;
    }
    commit2();
}
catch(...)
{
    rollback1();
    throw;
}
commit1();

На do_with_rollback тоже будет похожая вложенность.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.