Double operator() ( doublex, doubley ) const
// ^ Аргументы как аргументы оригинальной лямбды
{
return3.0 * x / ( y + 2.0 ); // <- Тело как в теле оригинальной лямбды
}
};
std::transform(
vX.begin(), vX.end(), vY.begin(),
std::back_inserter( vR ),
Labmda_1()
);
Итак, ЛЯМБДА-выражение в языке С++ - это неименованный функциональный объект, тело и код создания которого автоматически генерируется компилятором на основе заданных программистом инструкций. Лямбда-выражения с подходящими форматами аргументов и возвращаемым типом могут быть использованы во всех конструкциях, где могут использоваться вызываемые сущности.
Основное преимущество лямбда-выражений по сравнению с комбинацией связывателей и элементарных функторов состоит в простоте написания и восприятия человеком. С точки зрения генерируемого машинного кода оба способа одинаково эффективны. По сравнению с создаваемыми для однократного использования в том или ином алгоритме обычными функциями или пользовательскими функциональными объектами, лямбда-выражения намного проще - не требуется отдельно что-либо декларировать и создавать, развертывание происходит автоматически во время сборки программы.
Эту же идею можно применить для упрощения ранее созданной вспомогательной функции для вывода содержимого последовательности на экран - предоставляемое лямбда-выражение максимально простым способом задает что именно нужно сделать с каждым из элементов перебираемой последовательности:
template< typenameInputIt >
voidprintCollection ( InputIt _first, InputIt _last, const char* _prefix )
{
std::cout << _prefix;
std::for_each( _first, _last,
[] ( typenameInputIt::value_type x )
{
std::cout << x << ' ';
}
);
std::cout << std::endl;
}
Еще одним примером может служить радикальное упрощение размера и читабельности кода, задающего компаратор для сортировки сотрудников, комбинирующий фамилию и возраст. Перепишем этот код при помощи лямбда-выражений:
std::sort(
employees.begin(), employees.end(),
[] ( constEmployee * _pEmployee1, constEmployee * _pEmployee2 ) -> bool
{
if( _pEmployee1->getName() < _pEmployee2->getName() )
return true;
else if( _pEmployee1->getName() == _pEmployee2->getName() )
return_pEmployee1->getAge() < _pEmployee2->getAge();
Else
return false;
}
);
Очевидно, применение лямбд намного привлекательнее прежних устаревающих способов записи и особенно комбинирования функциональных объектов. В целом, лямбда-выражения значительно разгружают сложность использования функциональных объектов для управления поведением алгоритмов. Это делает более привлекательным также и использование стандартных алгоритмов STL, ранее скованное сложностью записи каких-либо нетривиальных предикатов для реально встречающихся на практике случаев.
При необходимости, лямбда-выражения могут быть вложенными, однако не стоит увлекаться написанием настолько сложного для восприятия кода. Обычно, крупное лямбда-выражение стоит разбить на несколько вспомогательных функций.
Std::function
Пусть в программе имеется три варианта сортировки данных о сотрудниках:
● в алфавитном порядке по фамилии;
● в порядке убывания зарплаты;
● в порядке убывания возраста.
Предположим, выбор способа сортировки зависит от какой-либо внешней настройки, например, от аргумента командной строки или свойства в конфигурационном файле. Для упрощения, возьмем, что способ сортировки передается в подпрограмму в виде enum-аргумента:
enum classEmployeeSortMode
{
Alphabetical,
Salaries,
Age
};
Сортировку поместим во вспомогательную функцию, принимающую ссылку на вектор сотрудников, а также выбранный режим сортировки. Простое решение состоит в использовании блока switch для выполнения различных способов сортировки в зависимости от выбранного режима:
voidsortEmployees (
std::vector< Employee * > & _employees,
EmployeeSortMode _sortMode
)
{
// Выбираем способ сортировки в зависимости от режима
switch( _sortMode )
{
// Сортировка по алфавиту
caseEmployeeSortMode::Alphabetical:
std::sort(
_employees.begin(), _employees.end(),
[] ( constEmployee * _pEmployee1,
constEmployee * _pEmployee2 )
{
return_pEmployee1->getName() < _pEmployee2->getName();
}
);
break;
// Сортировка по возрасту
caseEmployeeSortMode::Age:
std::sort(
_employees.begin(), _employees.end(),
[] ( constEmployee * _pEmployee1,
constEmployee * _pEmployee2 )
{
return_pEmployee1->getAge() > _pEmployee2->getAge();
}
);
break;
// Сортировка по зарплате
caseEmployeeSortMode::Salaries:
std::sort(
_employees.begin(), _employees.end(),
[] ( constEmployee * _pEmployee1,
constEmployee * _pEmployee2 )
{
return _pEmployee1->getSalary() > _pEmployee2->getSalary();
}
);
break;
// Неизвестный режим сортировки
default:
throwstd::logic_error( "Unexpected sort mode" );
}
}
Такой вариант решения содержит немало повторяющегося кода по вызову алгоритма sort. По сути, между режимами сортировки отличается только лямбда-выражение, определяющее критерий сравнения сотрудников. Для упрощения кода имеет смысл выделить выбор предиката в зависимости от режима в отдельную функцию, а в данной функции вызывать сортировку лишь однократно.
Это намерение моментально наталкивается на вопрос - а какой тип имеют эти лямбда-выражения? До лямбда-выражений мы бы выбирали бы функцию сравнения при помощи указателя на функцию определенной сигнатуры, однако каждый функциональный объект и, соответственно, каждое лямбда-выражение, имеют свой уникальный тип. В случае лямбда-выражений этот тип еще и полностью зависит от компилятора. В контексте шаблонов это никого не смущает, поскольку тип лямбда-выражения нигде в коде явно не указывается. В нашей же ситуации необходимо записать какой-то конкретный тип для возврата лямбда-выражения из вспомогательной функции, который подойдет к каждому из 3 лямбда-выражений.
В стандартной библиотеке имеется специальное средство std::function, представляющее собой универсальную абстракцию вызываемой сущности определенного формата. Объявление для такого объекта выглядит следующим довольно необычным образом (все же, немного симпатичней, чем указатели на функции):
std::function< bool( constEmployee *, constEmployee * ) > comparator;
^ ^
возвращаемый -------- формальные аргументы -----
тип
Объект std::function можно инициализировать одним из возможных вариантов вызываемой сущности:
● указателем на функцию:
boolMyEmployeeCompareFunction ( constEmployee *, constEmployee * );
comparator = & myEmployeeCompareFunction;
● функциональным объектом:
structMyEmployeeCompareFunctor
{
bool operator() ( constEmployee *, constEmployee * ) const;
};
comparator = & MyEmployeeCompareFunctor();
● лямбда-выражением:
comparator = [] ( constEmployee *, constEmployee * ) { … }
После инициализации объект std::function можно свободно передавать по значению или по ссылке. Окружающий std::function код вовсе не обязан быть шаблонным. Для вызова используется стандартный функциональный синтаксис:
boolresult = comparator( employees[ 0 ], employees[ 1 ] );
Итак, преобразуем программу к желаемому виду. Пусть выбор лямбда-выражения в зависимости от желаемого режима сортировки происходит в следующей функции:
std::function< bool( constEmployee *, constEmployee * ) >
getEmployeeSortingPredicate ( EmployeeSortMode _sortMode )
{
// Выбираем предикат для сортировки в зависимости от режима
switch( _sortMode )
{
// Сортировка по алфавиту
caseEmployeeSortMode::Alphabetical:
return[] ( constEmployee * _pEmployee1, constEmployee * _pEmployee2 )
{
return_pEmployee1->getName() < _pEmployee2->getName();
};
// Сортировка по возрасту
caseEmployeeSortMode::Age:
return[] ( constEmployee * _pEmployee1, constEmployee * _pEmployee2 )
{
return_pEmployee1->getAge() > _pEmployee2->getAge();
};
// Сортировка по зарплате
caseEmployeeSortMode::Salaries:
return[] ( constEmployee * _pEmployee1, constEmployee * _pEmployee2 )
{
return_pEmployee1->getSalary() > _pEmployee2->getSalary();
};
// Неизвестный режим сортировки
default:
throwstd::logic_error( "Unexpected sort mode" );
}
}
Основная функция сортировки заметно упрощается:
voidsortEmployees ( std::vector< Employee * > & _employees,
EmployeeSortMode _sortMode )
{
std::sort(
_employees.begin(),
_employees.end(),
getEmployeeSortingPredicate( _sortMode ) // выбор предиката по режиму
);
}
Протестируем решение при помощи простой тестовой программы:
intmain ()
{
// Набор тестовых данных о сотрудниках
std::vector< Employee * > employees;
employees.push_back( newEmployee( "Ivanov", 25, 1000.0 ) );
employees.push_back( newEmployee( "Ivanov", 45, 2000.0 ) );
employees.push_back( newEmployee( "Petrov", 50, 750.0 ) );
employees.push_back( newEmployee( "Sidorov", 34, 1200.0 ) );
// Сортировка по возрасту
std::cout << "===== By Age ===== " << std::endl;
sortEmployees( employees, EmployeeSortMode::Age );
printEmployees( employees );
// Сортировка по зарплате
std::cout << "===== By Salary ===== " << std::endl;
sortEmployees( employees, EmployeeSortMode::Salaries );
printEmployees( employees );
// Сортировка по алфавиту
std::cout << "===== By Name ===== " << std::endl;
sortEmployees( employees, EmployeeSortMode::Alphabetical );
printEmployees( employees );
// Освобождение объектов
std::for_each(
employees.begin(), employees.end(),
[] ( Employee * pE ) { deletepE; }
);
}
В результате запуска получим следующий результат: