Лямбда в развитии (C++11, C++14, C++17)

В этом посте содержится краткое описание того, что касалось лямбда-выражений в при их появлении в С++11 и как они развивались в последующих стандартах.

Лямбда-выражения - это упрощенная нотация для определения и использования анонимных объектов-функций вместо определения именованного класса с оператором 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

Инициализирующий захват




Комментарии

Популярные сообщения из этого блога

Полезные новации С++17. Выражения свертки (Fold expressions)

Перегрузка и специализация шаблонных функций.

Поддержка декомпозиции при объявлении (structural bindings) для классов