суббота, 14 ноября 2009 г.

C++0x Lambda Expressions

Одним из значительных нововведений в новом С++ являются лямбда выражения. Они перекочевали к нам от функциональных языков, сначала в С# 3.0, а теперь и в новый C++0x.
Лямбда выражения представляют собой небольшие программные конструкции, которые можно вставить прямо внутри какого либо выражения. Возможность оценить лямбда выражения дает нам новая Visual Studio 2010 и GCC.  Изначально их синтаксис может показаться довольно неудобным, но если к нему привыкнуть, они помогут вам сберечь много времени, а код сделать более лаконичным.

Приведем небольшой пример. Допустим есть вектор целых чисел и нам нужно посчитать, сколько чисел больше 5. С помощью лямбда выражений это делается весьма компактно.



Рассмотрим синтаксис лямбда выражения более подробно:


1) Список захвата или capture list
2) Список параметров
3) Изменение параметра, переданного по значению (может быть опущено)
4) Спецификация исключения (может быть опущено)
5) Возвращаемый тип
6) Тело лямбда выражения



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

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

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

Теперь подошли к самому интересному. Любое лямбда-выражение начинается с квадратных скобок - список захвата или capture list. Значит это следующее - выражения могут обращаться к внешним переменным, которые находятся в области видимости (как по значению так и по ссылке). Список между [] определяет к каким и как именно мы будем обращаться к внешним переменным. Таким образом предыдущий пример мы можем переписать в несколько измененном виде:



 Возможны следующие варианты:
 [] - Нет обращения к внешним переменным.
 [=] - Обращение ко всем внешним переменным по значению.
 [&] - Обращение ко всем внешним переменным по ссылке.
 [=, &x] - Обращение ко всем по значению, кроме переменной x
 [&, x] - Обращение ко всем по ссылке, кроме х.
 [x, &y, z] - Обращение только к переменным x, y, z (к y по ссылке). Доступ к другим переменным отсутствует.

Как видим списки захвата дают нам большую гибкость при работе с лямбда выражениями.

При обращении к внешним переменным нужно учесть несколько нюансов.
Допустим есть следующее лямбда выражение, в котором мы локально изменяем внешнюю переменную (обращаюсь по значению) и выводим ее на экран:




К сожалению такой код компилируется с ошибкой:
Error 1 error C3491: 'X': a by-value capture cannot be modified in a non-mutable lambda main.cpp 8 1 LambdaExpressions

Попробуем разобраться почему возникает такая ошибка. Сами по себе лямбда выражения представляют собой анонимные функторы, которые генерируется компилятором. Для выражения выше генерируется нечто следующее:

Вся проблема в том, что оператор генерируется с модификатором const, впоследствии чего переменная Х не может быть модифицирована. Решением проблемы является добавление ключевого слова mutable.


Второй нюанс при обращении к членам класса. Приведу сразу пример:

Компиляция этого кода также приведет к ошибке:
Error 1 error C3480: 'Object::X': a lambda capture variable must be from an enclosing function scope \main.cpp 11 1 LambdaExpressions

Компилятор говорит, что захватываемая переменная должна быть в области видимости. Для обращения к членам класса нужно вместо [X] объявить [this]. Код компилируется без проблем, но при этом обращение к членам класса осуществляется только по ссылке. Любые попытки сделать обращение по значению приведут к ошибке.

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

1 комментарий: