MyTetra Share
Делитесь знаниями!
Лямбда-функции в C++ (стандарт С++11)
Время создания: 14.01.2014 11:11
Автор: xintrea
Текстовые метки: язык, с++, C++11, лямбда, функция
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/1389683487h6t9rt07yg/text.html на raw.github.com

Пример использования лямбда-функции в C++

property<int> area = [&]{ return calculateArea(width, height); };

Если вы не знакомы с синтаксисом [&]{ ... }, то это лямбда-функция.

Постановка проблемы

Язык С++ содержит полезные встроенные функции, такие как std::for_each и std::transform, которые действительно нужны и необходимы. К сожалению, использование этих функций весьма громоздко и неудобно, особенно если функтор, который вы применяете, является уникальным для конкретной функции.

Пример:

#include <algorithm>

#include <vector>

namespace {

struct f {

void operator()(int) {

// do something

}

};

}

void func(std::vector<int>& v) {

f f;

std::for_each(v.begin(), v.end(), f);

}

Если вы используете объект f только один раз и только в конкретном месте, то писать целый класс (или структуру, как в примере) только для того, чтобы выполнить тривиальный код - это явное излишество.

В C++03 у вас может возникнуть соблазн расположить функтор рядом с вызывающим его кодом:

void func2(std::vector<int>& v) {

struct {

void operator()(int) {

// do something

}

} f;

std::for_each(v.begin(), v.end(), f);

}

Однако такое написание не допускается, так как в C++03 такая функция не может быть передана в шаблон.

Новое решение

В языке C++11 сделано нововведение - лямбда функции. Лямбда-функции пишутся прямо в месте своего использования, и не имеют имени (анонимны). Поэтому, вместо структуры struct f можно задать анонимный функтор. Для небольших простых примеров такую нотацию проще читать (ибо все в одном месте) и, возможно, такой код проще в обслуживании.

Пример, в простейшем виде:

void func3(std::vector<int>& v) {

std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });

}

В данном примере лямбда-функция - это всего лишь "синтаксический сахар", с помощью которого записывается анонимный функтор. Запись "[]" означает, что у функции нет имени, она безымянная, или, говоря по-другому, анонимная. Вместо "[]" можно мысленно подставлять имя "безымяннаяФункция", и думать о ней так: ее вызов будет выполнен в тот момент, когда до нее доходит указатель на команду (если вы понимаете, как работает машинный код).

Типы возвращаемых значений

В простейших случаях тип возвращаемого значения лямбда-функции выводится компилятором:

void func4(std::vector<double>& v) {

std::transform(v.begin(), v.end(), v.begin(),

[](double d) { return d < 0.00001 ? 0 : d; }

);

}

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

void func4(std::vector<double>& v) {

std::transform(v.begin(), v.end(), v.begin(),

[](double d) {

if (d < 0.0001) {

return 0;

}

else {

return d;

}

});

}

В вышеприведенном примере непонятно, какой тип будет в случае возвращения нуля - int или double, или еще какой.

Чтобы решить эту проблему, можно вручную указать возвращаемый тип лямбда-функии, используя синтаксис "-> тип":

void func4(std::vector<double>& v) {

std::transform(v.begin(), v.end(), v.begin(),

[](double d) -> double {

if (d < 0.0001) {

return 0;

}

else {

return d;

}

});

}

"Захват" переменных

Однако, ламбда-функция это не только анонимная функция, вызываемая в месте своего использования. Помимо прочего, лямбда-функция может использовать переменные, доступные в ее контексте. То есть переменные, доступные в том же контексте где и описана лямбда-функции, доступны внутри лямбда-функции. Это называется "замыкание".

Цитата из Википедии:

Замыкание (англ. closure) в программировании — функция, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции и не в качестве её параметров (а в окружающем коде). Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своём контексте.

Если вы хотите получить доступ к внешним переменным внутри лямбда-функции, можно использовать выражение "[]", в котором нужно указать захватываемую внешнюю переменную, которую можно будет использовать внутри лямбда-функции:

void func5(std::vector<double>& v, const double& epsilon) {

std::transform(v.begin(), v.end(), v.begin(),

[epsilon](double d) -> double {

if (d < epsilon) {

return 0;

}

else {

return d;

}

});

}

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

Помимо всего прочего, можно захватывать не только переменную. Можно захватывать ссылку на переменную. Помимо имен захватываемых переменных, можно указывать "правила захвата", обозначамые "&" и "=":

  • [&epsilon] - захват ссылки на переменную;
  • [&, epsilon] - показывает, что по-умолчанию захват переменных будет происходить по ссылке;
  • [=, &epsilon] - показывает, что по-умолчанию захват переменных будет происходить по значению, но для переменной epsilon захват должен производиться по ссылке;
  • [x, &y] - захват переменной x по значению, и переменной y по ссылке.

Следующая фраза выше моего понимания:

The generated operator() is const by default, with the implication that captures will be const when you access them by default. This has the effect that each call with the same input would produce the same result, however you can mark the lambda as mutable to request that the operator() that is produced is not const.

Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования