Есть ли причина не использовать глобальные лямбды?


89

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

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

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

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

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


Я предполагаю, что захват непреднамеренных переменных приведет к
большому

некоторые аргументы для этого youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf slash

Ответы:


61

Есть одна очень важная причина не использовать глобальные лямбды: потому что это не нормально.

Синтаксис регулярных функций C ++ существует со времен C. Программисты десятилетиями знали, что означает этот синтаксис и как они работают (хотя по общему признанию тот факт, что распад функции указателя иногда кусает даже опытных программистов). Если программист C ++ любого уровня квалификации, кроме «абсолютного новичка», видит определение функции, он знает, что получает.

Глобальная лямбда - это совсем другой зверь. Это поведение отличается от обычной функции. Лямбды - это объекты, а функции - нет. У них есть тип, но этот тип отличается от типа их функции. И так далее.

Итак, теперь вы подняли планку в общении с другими программистами. Программист C ++ должен понимать лямбды, если они хотят понять, что делает эта функция. И да, это 2019 год, так что приличный программист на С ++ должен иметь представление о том, как выглядит лямбда. Но это все еще более высокая планка.

И даже если они это поймут, вопрос у программиста будет такой: почему автор этого кода написал это так? И если у вас нет хорошего ответа на этот вопрос (например, потому что вы явно хотите запретить перегрузку, как в точках настройки Ranges), то вам следует использовать общий механизм.

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


9
"Со времен C" Путь до этого мой друг
Гонки

4
@LightnessRaces: Моя главная цель состояла в том, чтобы выразить, что синтаксис функций C ++ существует со времен C, поэтому многие люди знают, что это такое, путем проверки.
Никол Болас

2
Согласитесь со всем этим ответом, кроме первого предложения. «Это ненормально» - смутное и туманное утверждение. Возможно, вы имели в виду это в смысле «это необычно и запутанно»?
einpoklum

1
@einpoklum: C ++ имеет много вещей, которые можно считать «необычными и запутанными», которые все еще довольно «нормальны» в своей области (см. глубокое метапрограммирование шаблонов). Я думаю, что «нормальный» вполне допустим в этом случае; это не то, что находится в пределах "норм" опыта программирования на C ++, как исторически, так и сегодня.
Николь Болас

@NicolBolas: Но в этом смысле, это круговые рассуждения: OP задается вопросом, должны ли мы принять редкую практику. Редкие практики не являются "нормой", почти по определению. Но нм.
einpoklum

53

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

  • обычные функции могут быть перегружены; лямбды не могут (однако есть методы для симуляции)
  • Несмотря на то, что они подобны функциям, даже не захватившая лямбда-память будет занимать память (как правило, 1 байт для захвата).
    • как отмечено в комментариях, современные компиляторы оптимизируют это хранилище под правилом « как будто»

«Почему я не должен использовать лямбда-выражения для замены функторов с состоянием (классов)?»

  • классы просто имеют меньше ограничений, чем лямбды, и поэтому должны быть первыми, чего вы достигнете
    • (публичные / приватные данные, перегрузка, вспомогательные методы и т. д.)
  • если лямбда имеет состояние, то все труднее рассуждать о том, когда она станет глобальной.
    • Мы должны предпочесть создать экземпляр класса в самой узкой области видимости
  • уже трудно преобразовать не захватывающую лямбду в указатель на функцию, и это невозможно для лямбды, которая указывает что-либо в своем захвате.
    • классы дают нам простой способ создания указателей на функции, а также то, что многим программистам удобнее
  • Лямбды с любым захватом не могут быть построены по умолчанию (в C ++ 20. Ранее не было конструктора по умолчанию в любом случае)

Существует также неявный указатель «this» на лямбду (даже не захватывающую), что приводит к дополнительному параметру при вызове лямбды против вызова функции.
1201ProgramAlarm

@ 1201ProgramAlarm: это хороший момент; получить указатель на функцию для лямбды может быть намного сложнее (хотя
неперехватывающие

3
С другой стороны, если оно передается куда-то вместо прямого вызова, наличие лямбды вместо функции способствует встраиванию. Естественно, можно упаковать указатель на функцию std::integral_constantдля этого ...
Дедупликатор

@Deduplicator: « наличие лямбды вместо функции способствует встраиванию ». Для ясности, это применимо, только если «где-то» - это шаблон, который принимает вызываемую функцию как произвольный вызываемый тип, а не как указатель на функцию.
Никол Болас

2
@AndyG Вы забываете правило «как будто»: программы, которые не запрашивают размер лямбды (потому что… почему ?!) и не создают указатель на нее, не нуждаются (и обычно этого не делают) выделить место для этого.
Конрад Рудольф

9

Спросив, я подумал о причине, по которой не следует этого делать: поскольку это переменные, они подвержены статическому порядку инициализации Fiasco ( https://isocpp.org/wiki/faq/ctors#static-init-order ), который может вызвать ошибки по линии.


Вы можете сделать их constexprлямбдами ... Суть в том, чтобы просто использовать функцию.
einpoklum

8

Есть ли причина не использовать везде лямбды вместо «обычных» глобальных функций?

Задача определенного уровня сложности требует решения, по крайней мере, такой же сложности. Но если есть и менее сложное решение для той же проблемы, тогда нет никаких оснований для использования более сложного. Зачем вводить сложность, которая вам не нужна?

Между лямбдой и функцией функция является просто менее сложным видом сущности из двух. Вы не должны оправдывать не использование лямбды. Вы должны оправдать использование одного. Лямбда-выражение вводит тип замыкания, который является безымянным типом класса со всеми обычными специальными функциями-членами, оператором вызова функции и, в этом случае, неявным оператором преобразования в указатель на функцию, и создает объект этого типа. Инициализация копирования глобальной переменной из лямбда-выражения просто делает намного больше, чем просто определение функции. Он определяет тип класса с шестью неявно объявленными функциями, определяет еще две операторные функции и создает объект. Компилятор должен сделать намного больше. Если вам не нужны какие-либо функции лямбды, не используйте лямбду…


6

Лямбды - это анонимные функции .

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


1
Кажется, это аргумент из терминологии, т.е. запутанные названия и смысл. Это можно легко опровергнуть, указав, что именованная лямбда больше не является анонимной (дух). Проблема здесь не в том, как используется лямбда, а в том, что «анонимная функция» является неправильным и не точна, когда лямбда привязана к имени.
Конрад Рудольф

@KonradRudolph: Это не просто терминология, поэтому они были созданы в первую очередь. По крайней мере, исторически, лямбды - это анонимные функции, и поэтому их сложно назвать, особенно когда вместо них ожидаются функции.
Эрик

1
Неа. Лямбды обычно называют (только обычно локально), в этом нет ничего запутывающего.
Конрад Рудольф

1
Не согласен с анонимными функциями , это скорее функтор, но в любом случае, я не вижу проблемы в том, чтобы иметь (именованный) экземпляр вместо того, чтобы заставлять использовать их только как rvalue-reference. (так как ваша точка зрения также должна применяться на местном уровне).
Jarod42

5

если есть какая-то причина не оставлять это как лямбду? Есть ли причина не использовать везде лямбды вместо «обычных» глобальных функций?

Мы привыкли использовать функции вместо глобального функтора, поэтому он нарушает согласованность и принцип наименьшего удивления .

Основными отличиями являются:

  • функции могут быть перегружены, а функторы - нет.
  • функции можно найти с помощью ADL, а не функторов.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.