Лямбда-выражения в C++
В C++11 и более поздних версиях лямбда-выражение, часто называемое лямбда-выражением, является удобным способом определения анонимного объекта функции ( закрытия) непосредственно в месте, где он вызывается или передается в качестве аргумента функции. Обычно лямбда-выражения используются для инкапсуляции нескольких строк кода, которые передаются алгоритмам или асинхронным функциям. В этой статье описывается, что такое лямбда-выражения, и сравнивается их с другими методами программирования. Здесь описываются их преимущества и приводятся некоторые основные примеры.
Похожие статьи
- Сравнение лямбда-выражений и объектов функций
- Работа с лямбда-выражениями
- Лямбда-выражения constexpr
Части лямбда-выражения
Ниже приведена простая лямбда-выражение, передаваемая в качестве третьего аргумента функции std::sort()
:
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
На этом рисунке показаны части лямбда-синтаксиса:
Пример лямбда-выражения — [=]() mutable throw() -> int { return x+y; } [=] — это предложение отслеживания; также известный как лямбда-введение в спецификации C++. Круглые скобки относятся к списку параметров. Ключевое слово mutable является необязательным. throw() — необязательная спецификация исключения. -> int является необязательным конечным типом возвращаемого значения. Тело лямбда-выражения состоит из оператора внутри фигурных скобок или возврата x+y; Они подробно описаны на рисунке.
Предложение capture (также известное как лямбда-вводное выражение в спецификации C++).
список параметров Дополнительные. (Также называется лямбда-декларатором)
изменяемая спецификация Дополнительные.
спецификация исключений Дополнительные.
тип возвращаемого значения Дополнительные.
Лямбда-тело.
Предложение Capture
Лямбда-выражение может вводить новые переменные в своем теле (в C++14), а также получать доступ к переменным из окружающей области или захватывать их. Лямбда-выражение начинается с предложения capture. Он указывает, какие переменные записываются, а также указывает, выполняется ли запись по значению или по ссылке. Доступ к переменным с префиксом амперсанда (&
) можно получить по ссылке, а переменным, у которых нет этого префикса, — по значению.
Пустое предложение фиксации ([ ]
) показывает, что тело лямбда-выражения не осуществляет доступ к переменным во внешней области видимости.
Вы можете использовать режим записи по умолчанию, чтобы указать, как захватывать любые внешние переменные, на которые ссылается лямбда-текст: [&]
означает, что все переменные, на которые вы ссылаетесь, захватываются по ссылке, а [=]
означает, что они фиксируются по значению. Можно сначала использовать режим фиксации по умолчанию, а затем применить для определенных переменных другой режим. Например, если тело лямбда-выражения осуществляет доступ к внешней переменной total
по ссылке, а к внешней переменной factor
по значению, следующие предложения фиксации эквивалентны:
[&total, factor]
[factor, &total]
[&, factor]
[=, &total]
При использовании параметра capture-default записываются только переменные, упомянутые в лямбда-тексте.
Если предложение отслеживания содержит свойство capture-default &
, то идентификатор в записи этого предложения отслеживания не может иметь форму &identifier
. Аналогичным образом, если предложение отслеживания содержит свойство capture-default =
, то никакой захват этого предложения отслеживания не может иметь форму =identifier
. Идентификатор или this
не может отображаться более одного раза в предложении отслеживания. В следующем фрагменте кода показаны некоторые примеры:
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[=, *this]{ }; // OK: captures this by value. See below.
[i, i]{}; // ERROR: i repeated
}
Захват с последующим многоточием представляет собой расширение пакета, как показано в следующем примере с вариадным шаблоном :
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
Чтобы использовать лямбда-выражения в теле функции-члена класса, передайте this
указатель на предложение capture, чтобы предоставить доступ к функциям-членам и членам данных включаемого класса.
Visual Studio 2017 версии 15.3 и более поздних версий (доступно в /std:c++17
режиме и более поздних версиях): this
указатель можно записать по значению, указав *this
в предложении capture. Захват по значению копирует все закрытие на каждый сайт вызова, где вызывается лямбда-выражение. (Закрытие — это объект анонимной функции, инкапсулирующий лямбда-выражение.) Запись по значению полезна, когда лямбда-выражения выполняются в параллельных или асинхронных операциях. Это особенно полезно для определенных архитектур оборудования, таких как NUMA.
Пример использования лямбда-выражений с функциями-членами класса см. в разделе "Пример: использование лямбда-выражения в методе" статьи Примеры лямбда-выражений.
При использовании предложения capture рекомендуется учитывать следующие моменты, особенно при использовании лямбда-выражений с многопотоковой:
Записи ссылок можно использовать для изменения переменных за пределами, а записи значений — нет. (
mutable
позволяет изменять копии, но не оригиналы.)Записи ссылок отражают обновления переменных за пределами, а записи значений — нет.
Фиксация ссылки вводит зависимость от времени существования, тогда как фиксация значения не обладает зависимостями от времени существования. Это особенно важно, когда лямбда-выражение выполняется асинхронно. Если записать локальный объект по ссылке в асинхронном лямбда-коде, он может быть легко удален к моменту выполнения лямбда-выражения. Код может привести к нарушению доступа во время выполнения.
Обобщенная фиксация (C++14)
В C++14 можно вводить и инициализировать новые переменные в предложении capture, не требуя, чтобы эти переменные существовали во включающей области лямбда-функции. Инициализация может быть выражена в качестве любого произвольного выражения. Тип новой переменной определяется типом, который создается выражением. Эта функция позволяет захватывать переменные только для перемещения (например, std::unique_ptr
) из окружающей области и использовать их в лямбда-выражениях.
pNums = make_unique<vector<int>>(nums);
//...
auto a = [ptr = move(pNums)]()
{
// use ptr
};
Список параметров
Лямбда-выражения могут захватывать переменные и принимать входные параметры. Список параметров (лямбда-декларатор в стандартном синтаксисе) является необязательным и в большинстве аспектов напоминает список параметров для функции.
auto y = [] (int first, int second)
{
return first + second;
};
В C++14, если тип параметра является универсальным auto
, в качестве описателя типа можно использовать ключевое слово . Это ключевое слово указывает компилятору создать оператор вызова функции в качестве шаблона. Каждый экземпляр auto
в списке параметров эквивалентен параметру отдельного типа.
auto y = [] (auto first, auto second)
{
return first + second;
};
Лямбда-выражение может принимать другое лямбда-выражение в качестве своего аргумента. Дополнительные сведения см. в разделе "Лямбда-выражения более высокого порядка" статьи Примеры лямбда-выражений.
Так как список параметров является необязательным, можно опустить пустые круглые скобки, если не передать аргументы лямбда-выражению, а его лямбда-декларатор не содержит спецификацию исключения, тип возвращаемого значения или mutable
.
Изменяемая спецификация
Как правило, оператор вызова функции лямбда-выражения констант по значению mutable
, но использование ключевого слова отменяет это. Он не создает изменяемые элементы данных. Спецификация mutable
позволяет тексту лямбда-выражения изменять переменные, захваченные значением. В некоторых примерах далее в этой статье показано, как использовать mutable
.
Спецификация исключений
Можно использовать спецификацию исключений noexcept
, чтобы указать, что лямбда-выражение не создает никаких исключений. Как и в случае с обычными функциями, компилятор Microsoft C++ создает предупреждение C4297 , если лямбда-выражение объявляет спецификацию noexcept
исключения, а тело лямбда-выражения создает исключение, как показано ниже:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() noexcept { throw 5; }();
}
Дополнительные сведения см. в разделе Спецификации исключений (исключение).
Возвращаемый тип
Возвращаемый тип лямбда-выражения выводится автоматически. Не нужно использовать ключевое слово , auto
если не указан тип конечного возвращаемого значения. Конечный тип возврата напоминает часть возвращаемого типа обычной функции или функции-члена. Однако тип возвращаемого значения следует списку параметров, и необходимо включить ключевое слово ->
элемента trailing-return-type перед типом возвращаемого значения.
Можно опустить часть возвращаемого типа лямбда-выражения, если тело лямбда-выражения содержит только один оператор return. Или, если выражение не возвращает значение. Если тело лямбда-выражения содержит один оператор return, компилятор выводит тип возвращаемого значения из типа возвращаемого выражения. В противном случае компилятор выводит тип возвращаемого значения как void
. Рассмотрим следующий пример фрагментов кода, иллюстрирующих этот принцип:
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list isn't valid
Лямбда-выражение может создавать другое лямбда-выражение в качестве своего возвращаемого значения. Дополнительные сведения см. в разделе "Лямбда-выражения более высокого порядка" статьи Примеры лямбда-выражений.
Тело лямбда-выражения
Лямбда-тело лямбда-выражения является составным оператором. Он может содержать все, что разрешено в теле обычной функции или функции-члена. Тело обычной функции и лямбда-выражения может осуществлять доступ к следующим типам переменных:
Фиксированные переменные из внешней области видимости (см. выше).
Параметры.
Локально объявленные переменные.
Члены данных класса, если они объявлены внутри класса и
this
захватываются.Любая переменная со статической длительностью хранения, например глобальные переменные.
В следующем примере содержится лямбда-выражение, которое явно фиксирует переменную n
по значению и неявно фиксирует переменную m
по ссылке.
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
5
0
Поскольку переменная n
фиксируется по значению, ее значение после вызова лямбда-выражения остается равным 0
. Спецификация mutable
позволяет n
изменяться в лямбда-выражении.
Лямбда-выражение может записывать только переменные с автоматической длительностью хранения. Однако можно использовать переменные со статической длительностью хранения в теле лямбда-выражения. В следующем примере функция generate
и лямбда-выражение используются для присвоения значения каждому элементу объекта vector
. Лямбда-выражение изменяет статическую переменную для получения значения следующего элемента.
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this isn't thread-safe and is shown for illustration only
}
Дополнительные сведения см. в разделе Создание.
В следующем примере кода используется функция из предыдущего примера и добавляется пример лямбда-выражения, использующего алгоритм generate_n
стандартной библиотеки C++. Это лямбда-выражение назначает элемент объекта vector
сумме предыдущих двух элементов. Ключевое mutable
слово используется для того, чтобы текст лямбда-выражения может изменять свои копии внешних переменных x
и y
, которые лямбда-выражение захватывает по значению. Поскольку лямбда-выражение захватывает исходные переменные x
и y
по значению, их значения остаются равными 1
после выполнения лямбда-выражения.
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this isn't thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18
Дополнительные сведения см. в разделе generate_n.
constexpr
Лямбда-выражения
Visual Studio 2017 версии 15.3 и более поздних версий (доступно в /std:c++17
режиме и более поздних версиях). Вы можете объявить лямбда-выражение как constexpr
(или использовать его в константном выражении), если разрешена инициализация каждого записанного или введенного элемента данных в константном выражении.
int y = 32;
auto answer = [y]() constexpr
{
int x = 10;
return y + x;
};
constexpr int Increment(int n)
{
return [n] { return n + 1; }();
}
Лямбда неявно constexpr
, если его результат соответствует требованиям constexpr
функции:
auto answer = [](int n)
{
return 32 + n;
};
constexpr int response = answer(10);
Если лямбда-выражение является неявным или явным образом constexpr
, преобразование в указатель функции создает функцию constexpr
:
auto Increment = [](int n)
{
return n + 1;
};
constexpr int(*inc)(int) = Increment;
Специально для систем Майкрософт
Лямбда-выражения не поддерживаются в следующих управляемых сущностях среды CLR: ref class
, ref struct
, или value class
value struct
.
Если вы используете модификатор майкрософт, __declspec
например , его можно вставить в лямбда-выражение сразу после parameter-declaration-clause
. Пример:
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
Чтобы определить, поддерживается ли определенный модификатор лямбда-выражениями, см. статью о модификаторе в разделе Модификаторы, относящиеся к Майкрософт .
Visual Studio поддерживает лямбда-функции C++11 Standard и лямбда-выражения без отслеживания состояния. Лямбда-выражения без отслеживания состояния можно преобразовать в указатель функции, использующий соглашение о произвольных вызовах.
См. также
Справочник по языку C++
Объекты-функции в стандартной библиотеке C++
Вызов функции
for_each