Использование lambdas для выполнения вложенных функций

Что все считают, что использование lambdas для выполнения вложенных функций в С++? Например, вместо этого:

static void prepare_eggs()
{
   ...
}

static void prepare_ham()
{
   ...
}

static void prepare_cheese()
{
   ...
}

static fry_ingredients()
{
   ...
}

void make_omlette()
{
    prepare_eggs();
    prepare_ham();
    prepare_cheese();
    fry_ingredients();
}

Вы делаете это:

void make_omlette()
{
    auto prepare_eggs = [&]()
    {
       ...
    };

    auto prepare_ham = [&]()
    {
       ...
    };

    auto prepare_cheese = [&]()
    {
       ...
    };

    auto fry_ingredients = [&]()
    {
       ...
    };


    prepare_eggs();
    prepare_ham();
    prepare_cheese();
    fry_ingredients();
}

Придя из поколения, которое научилось программировать с помощью Pascal, вложенные функции имеют для меня смысл. Однако это использование, похоже, путало некоторых из менее опытных разработчиков в моей группе при работе во время обзора кода, где я использовал lambdas таким образом.

Ответ 1

Я не вижу ничего плохого в вложенных функциях как таковых. Я использую lambdas для вложенных функций, но только если он удовлетворяет некоторым условиям:

  • Он вызывается не один раз. (иначе просто напишите код напрямую, если он не слишком длинный)
  • Это действительно внутренняя функция, так что вызов ее в любом другом контексте не имеет смысла.
  • Это достаточно короткий (возможно, 10 строк).

Итак, в вашем примере я бы не использовал лямбда для причины номер один.

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

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

Ответ 2

Для любого данного фрагмента кода сделайте его видимым по мере необходимости и как можно более скрытым:

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

Ответ 3

Вы уже можете догадаться, что вы делаете что-то неортодоксальное по полученным вами комментариям. Это одна из причин, по которой С++ имеет плохую репутацию, люди никогда не перестают ее злоупотреблять. Lambdas в основном используются в качестве встроенных функциональных объектов для стандартных библиотечных алгоритмов и мест, требующих своего рода механизма обратного вызова. Я думаю, что это охватывает 99% случаев использования, и он должен оставаться таким!

Как сказал Бьярне в одной из своих лекций: "Не все должно быть шаблоном, и не все должно быть объектом".

И не все должно быть лямбдой:) нет ничего плохого в функции свободного стояния.

Ответ 4

По-моему, это бесполезно, но вы можете сделать это, используя только С++ 03

void foo() {
  struct {
    void operator()() {}
  } bar;
  bar();
}

Но опять же, ИМХО это бесполезно.

Ответ 5

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

Но в то же время функциональность должна быть локальной или достаточно конкретной, чтобы у меня не было стимула реорганизовать функциональность вне (не такой) закрывающей функции, где я мог бы, возможно, повторно использовать ее полностью в другой функции на какой-то пункт. Он также должен быть коротким: иначе я просто собираюсь его переместить, возможно, помещая его в анонимное пространство имен (или namespace detail в заголовке) или некоторые из них. Мне не нужно много торговать местностью в пользу компактности (длинные функции - это боль для обзора).

Обратите внимание, что это строго языковое агностическое. Я не думаю, что С++ вращает на это особое вращение. Если есть один конкретный совет на С++, я должен дать на эту тему, однако, это то, что я бы запретил использование по умолчанию привязки по ссылке ([&]). Невозможно было бы определить, описывает ли это конкретное лямбда-выражение замыкание или локальную функцию без тщательного анализа всего тела. Что бы это было не так плохо (это не то, что закрытие "страшно" ), если не для того, что эта привязка по ссылке ([&], [&foo]) допускает мутации, даже если лямбда не отмечена mutable и по -value capture ([=], [foo]) может сделать нежелательную копию или даже попытаться сделать невозможную копию для типов только для перемещения. В общем, я бы предпочел вообще ничего не захватить, если это возможно (какие параметры для!), И при необходимости использовать отдельные захваты. Это особенно проблематично

Подводя итог:

// foo is expensive to copy, but ubiquitous enough
// that capturing it rather than passing it as a parameter
// is acceptable
auto const& foo_view = foo;
auto do_quux = [&foo_view](arg_type0 arg0, arg_type1 arg1) -> quux_type
{
    auto b = baz(foo_view, arg0, arg1);
    b.frobnicate;
    return foo_view.quux(b);
};

// use do_quux several times later