Evgenii Legotckoi
Ақп. 14, 2018, 5:13 Т.Ж.

C++ тілінде Lambda функцияларын пісіру - 1 бөлім

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

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

Возможные варианты синтаксиса лямбда функций

  1. [ capture ] ( params ) mutable exception attribute -> ret { body }
  2. [ capture ] ( params ) -> ret { body }
  3. [ capture ] ( params ) { body }
  4. [ capture ] { body }

Первый вариант является полным, но не запрещается использовать сокращённые вариации записи функций.

  • capture - список  внешних захватываемых объектов, они могут захватываться как по ссылке, так и копированием.
  • params - список параметров, передаваемых в лямбда функции, данная часть будет аналогична записи аргументов для обычных функций.
  • mutable - использование mutable позволяет модифицировать копии объектов, которые были захвачены копированием. В обычном варианте они не будут модифицироваться.
  • exception - обеспечивает спецификацию исключения, то есть лямбда функции также как и обычные функции могут выкидывать исключения.
  • attribute - обеспечивает спецификацию атрибута, таких атрибутов в спецификации C++ определено всего два ([[noreturn]], [[carries_dependency]])
  • params - список параметров, передаваемых в лямбда функцию
  • ret - возвращаемое значение лямбда функции

Что касается возвращаемого значение, то оно может автоматически выводиться из типа объекта, который возвращается оператором return. Если же в лямбда-функции отсутствует оператор return, то возвращаемое значение будет void.

Лямбда функция создаёт безымянный временный объект уникального безымянного non-union, non-aggregate типа, известного как тип замыкания. Благодаря введению оператора auto в современном стандарте C++ можно объявить объект лямбда функции довольно легко, без прописывания объявления функтора ( std::function ) со всеми апраметрами и возвращаемыми значениями, что делает код более простым и читаемым (для опытного программиста, конечно. Безусловно нужно учитывать то, что новичок быстрее заподозрит неладное, если в объявлении лямбды будет фигурировать std::function, но это уже вопрос практики).

Вот пример объявления простой лямбда функции, которая будет возвращать тип void , поскольку отсутствует хотя бы один оператор return .

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. int main()
  6. {
  7. auto myLambda = []()
  8.   {
  9. cout << "Hello World!" << endl;
  10. };
  11.  
  12. myLambda();
  13.  
  14. return 0;
  15. }

Соответственно программный код не скомпилируется, если в лямда-функции будет присутствовать два и более оператора return, которые будут возвращать объекты разных типов, не связанных между собой иерархией наследования и не способные быть приведены к типу базового класса. И даже, если эти объекты имеют базовый класс, необходимо будет прописать тип возвращаемого значения, им как раз будет указатель на объект базового класса (в общем случае).

Вот пример кода, который не скомпилируется.

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. class A
  6. {
  7. public:
  8. A(){}
  9. };
  10.  
  11. class B : public A
  12. {
  13. public:
  14. B(){}
  15. };
  16.  
  17. class C : public A
  18. {
  19. public:
  20. C(){}
  21. };
  22.  
  23. int main()
  24. {
  25. auto myLambda = [](int type)
  26.   {
  27. if (type == 0)
  28. {
  29. return new B();
  30. }
  31. else
  32. {
  33. return new C();
  34. }
  35. };
  36.  
  37. myLambda(0);
  38.  
  39. return 0;
  40. }

Нужно указать тип возвращаемого значения

  1. auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения
  2. {
  3. if (type == 0)
  4. {
  5. return new B();
  6. }
  7. else
  8. {
  9. return new C();
  10. }
  11. };

Также ошибка компиляции будет в том случае, если не указать тип возвращаемого значения и при этом вы создаёте в куче объект внутри лямбда функции, но в некоторых случаях можете вернуть указатель на nullptr. То есть ниже следующий код не скомпилируется.

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. class A
  6. {
  7. public:
  8. A(){}
  9. };
  10.  
  11.  
  12. int main()
  13. {
  14. auto myLambda = [](int type)
  15. {
  16. if (type == 0)
  17. {
  18. return new A();
  19. }
  20. else
  21. {
  22. return nullptr;
  23. }
  24. };
  25.  
  26. myLambda(0);
  27.  
  28. return 0;
  29. }

Опять нужно указать тип возвращаемого значения

  1. auto myLambda = [](int type) -> A* // Укажем тип возвращаемого значения
  2. {
  3. if (type == 0)
  4. {
  5. return new A();
  6. }
  7. else
  8. {
  9. return nullptr;
  10. }
  11. };

Дело в том, что nullptr - это универсальный тип данных, который в каком-то смысле не является типом данных, поскольку его нельзя установить в качестве типа переменной. Но он может быть присвоен в качестве значения указателю на объект. Чтобы неявное преобразование в данном случае происходило правильно, нужно также указать тип возвращаемого значения.

Также в выше приведённом примере показано, как вызвать лямда функцию и передать в неё параметры. Заметили? В данном примере используется параметр int type , в зависимости от которого мы возвращаем указатель на созданный объект или nullptr .

Также в лямбда функциях присутствует понятие захвата переменных. Это означает, что лямбда функция может использовать не только переменные, которые передаются ей в качестве параметров, но и какие-либо объекты, которые были объявлены вне лямда-функции.

Список символов может быть передан следующим образом:

    1. **[a,&b]**
    где a захвачена по значению, а b захвачена по ссылке.
    1. **[this]**
    захватывает указатель
    1. **this**
    по значению.
    1. **[&]**
    захват всех символов по ссылке
    1. **[=]**
    захват всех символов по значению
    1. **[]**
    ничего не захватывает

Про захват переменных поговорим в следующих статьях.

Но отмечу один интересный момент, лямда-функцию можно вызвать сразу же там, где вы её и объявили, если добавить после тела лямда функции круглые скобки и передать все необходимые параметры, если они имеются.

Например такой код тоже скомпилируется

  1. #include <iostream>
  2.  
  3. using namespace std;
  4.  
  5. class A
  6. {
  7. public:
  8. A(){}
  9. };
  10.  
  11.  
  12. int main()
  13. {
  14.  
  15. A* myA = [](int type) -> A*
  16. {
  17. if (type == 0)
  18. {
  19. return new A();
  20. }
  21. else
  22. {
  23. return nullptr;
  24. }
  25. }(0);
  26.  
  27. return 0;
  28. }

Так что подумайте, скомпилируется ли следующий программный код?

  1. int main(){[](){}();}

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз