Лямбда в развитии (C++11, C++14, C++17)
В этом посте содержится краткое описание того, что касалось лямбда-выражений в при их появлении в С++11 и как они развивались в последующих стандартах.
Лямбда-выражения - это упрощенная нотация для определения и использования анонимных объектов-функций вместо определения именованного класса с оператором operator(), последующим созданием объекта этого класса и вызовом его.
Основы:
Минимальная лямбда содержит 3 части:
Как видно - при создании лямбды вызывается конструктор копирования, соответственно деструктор вызывается дважды - для явно создаваемого объекта и для его копии, захваченной лямбдой при выходе из области её видимости.
Лямбда-выражения - это упрощенная нотация для определения и использования анонимных объектов-функций вместо определения именованного класса с оператором operator(), последующим созданием объекта этого класса и вызовом его.
Основы:
Минимальная лямбда содержит 3 части:
- [] - захват,
- () - параметры,
- {} - тело.
Последующая лямбда увеличивает на 1 все элементы вектора.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto my_lambda = [](int x) { return x + 1; };
std::transform(v.begin(), v.end(), v.begin(), my_lambda);
for (auto &value : v) {
std::cout << value << std::endl;
}
return 0;
}
По поводу захватов по ссылке и по значению, а также проблемы "висячих" ссылок можно почитать у Мейерса тут в Chapter 6.
Что добавилось в С++14:
- Инициализирующий захват. Захват с объявлением и инициализацией переменной типа auto, чья область видимости совпадает с телом лямбда-выражения;
- Обобщенные лямбда-выражения. Теперь лямбда принимает auto как тип параметра.
Пример инициализирующего захвата:
#include <iostream>
int main() {
int x = 1;
auto my_lambda = [&z = x]() { z += 1; };
my_lambda();
std::cout << x << std::endl;
return 0;
}
Пример обобщенной лямбды:
#include <iostream>
int main() {
auto my_lambda = [](auto &a, auto &b) { return a < b; };
float af = 1.5, bf = 2.0;
int ai = 3, bi = 1;
std::cout << "Float: " << my_lambda(af, bf) << std::endl;
std::cout << "Integer: " << my_lambda(ai, bi) << std::endl;
return 0;
}
Что добавилось в С++17:
- constexpr лямбды. Это позволяет вызывать лямбда-выражения и использовать их результат во время компиляции;
- Захват *this. Это позволяет лямбде захватывать окружающий объект копированием. Это позволяет безопасно использовать лямбду даже когда окружающий объект был разрушен;
C лямбдами во время компиляции всё выглядит так:
#include <iostream>
int main() {
constexpr auto multi = [](int a, int b){ return a * b; };
static_assert(multi(3,7) == 21, "3x7 == 21");
static_assert(multi(4,5) == 15, "4x5 != 15");
return 0;
}
Захват *this проиллюстрируем так:
#include <iostream>
class MyClass
{
int x;
public:
MyClass(int _x):
x(_x)
{
std::cout << "ctor" << std::endl;
}
MyClass(const MyClass& _ms):
x(_ms.x)
{
std::cout << "copy ctor" << std::endl;
}
~MyClass()
{
std::cout << "dtor" << std::endl;
}
auto GetLambda();
};
auto MyClass::GetLambda()
{
return [*this]()
{
std::cout << "C++ 17 lambda call x = " << x << std::endl;
};
}
int main()
{
{
MyClass* pMC = new MyClass(777);
auto PostMortemLambda = pMC->GetLambda();
delete pMC;
PostMortemLambda();
}
std::cout << "Bye-bye" << std::endl;
return 0;
}
Вывод программы (GCC 8.2.0):
ctor copy ctor dtor C++ 17 lambda call x = 777 dtor Bye-bye
Как видно - при создании лямбды вызывается конструктор копирования, соответственно деструктор вызывается дважды - для явно создаваемого объекта и для его копии, захваченной лямбдой при выходе из области её видимости.
PS. Использована статья.
лось в С++14
Инициализирующий захват
Комментарии
Отправить комментарий