Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
// той же длины
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b)
{ return a.size() < b.size(); });
// получить итератор на первый элемент, размер которого >= sz
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a)
{ return a.size() >= sz; });
// вычислить количество элементов с размером >= sz
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s")
<< " of length " << sz << " or longer" << endl;
// вывести слова, размер которых равен или больше заданного, разделяя
// их пробелами
for_each(wc, words.end(),
[](const string &s) {cout << s << " ";});
cout << endl;
}
Упражнения раздела 10.3.2Упражнение 10.14. Напишите лямбда-выражение, получающее два целых числа и возвращающее их сумму.
Упражнение 10.15. Напишите лямбда-выражение, захватывающее переменную типа int окружающей функции и получающей параметр типа int. Лямбда-выражение должно возвратить сумму захваченного значения типа int и параметра типа int.
Упражнение 10.16. Напишите собственную версию функции biggies(), используя лямбда-выражения.
Упражнение 10.17. Перепишите упражнение 10.12 из раздела 10.3.1 так, чтобы в вызове функции sort() вместо функции compareIsbn() использовалось лямбда-выражение.
Упражнение 10.18. Перепишите функцию biggies() так, чтобы использовать алгоритм partition() вместо алгоритма find_if(). Алгоритм partition() описан в упражнении 10.13 раздела 10.3.1.
Упражнение 10.19. Перепишите предыдущее упражнение так, чтобы использовать алгоритм stable_partition(), который, подобно алгоритму stable_sort(), обеспечивает исходный порядок элементов в разделяемой последовательности.
10.3.3. Захват и возвращение значений лямбда-выражениями
При определении лямбда-выражения компилятор создает новый (безымянный) класс, соответствующий этому лямбда-выражению. Создание этих классов рассматривается в разделе 14.8.1, а пока следует понять, что при передаче лямбда-выражения функции определяется новый тип и создается его объект. Безымянный объект этого созданного компилятором типа и передается как аргумент. Аналогично при использовании ключевого слова auto для определения переменной, инициализированной лямбда-выражением, определяется объект типа, созданного из этого лямбда-выражения.
По умолчанию созданный из лямбда-выражения класс содержит переменные-члены, соответствующие захваченным переменным лямбда-выражения. Подобно переменным-членам любого класса, переменные-члены лямбда-выражения инициализируются при создании его объекта.
Захват по значениюПодобно передаче параметров, переменные можно захватывать по значению или по ссылке. В табл. 10.1 приведены различные способы создания списка захвата. До сих пор у использованных лямбда-выражений захват переменных осуществлялся по значению. Подобно передаче по значению параметров, захват переменной по значению подразумевает ее копирование. Но, в отличие от параметров, копирование значения при захвате осуществляется при создании лямбда-выражения, а не при его вызове:
void fcnl() {
size_t v1 = 42; // локальная переменная
// копирует v1 в вызываемый объект f
auto f = [v1] { return v1; };
v1 = 0;
auto j = f(); // j = 42; f получит копию v1 на момент создания
}
Поскольку значение копируется при создании лямбда-выражения, последующие изменения захваченной переменной никак не влияют на соответствующее значение в лямбда-выражении.
Таблица 10.1. Список захвата лямбда-выражения
[] Пустой список захвата. Лямбда-выражение не может использовать переменные из содержащей функции. Лямбда-выражение может использовать локальные переменные, только если оно захватывает их [names] names — разделяемый запятыми список имен, локальных для содержащей функции. По умолчанию переменные в списке захвата копируются. Имя, которому предшествует знак &, захватывается по ссылке [&] Неявный захват по ссылке. Сущности из содержащей функции используются в теле лямбда-выражения по ссылке [=] Неявный захват по значению. Сущности из содержащей функции используются в теле лямбда-выражения как копии [&, identifier_list] identifier_list — разделяемый запятыми список любого количества переменных из содержащей функции. Эти переменные захватываются по значению; любые неявно захваченные переменные захватывается по ссылке. Именам в списке identifier_list не могут предшествовать символы & [=, reference_list] Переменные, включенные в список reference_list, захватываются по ссылке; любые неявно захваченные переменные захватывается по значению. Имена в списке reference_list не могут включать часть this и должны предваряться символом & Захват по ссылкеМожно также определять лямбда-выражения, захватывающие переменные по ссылке. Например:
void fcn2() {
size_t v1 = 42; // локальная переменная
// объект f2 содержит ссылку на v1
auto f2 = [&v1] { return v1; };
v1 = 0;
auto j = f2(); // j = 0; f2 ссылается на v1; он не хранится в j
}
Символ & перед v1 указывает, что переменная v1 должна быть захвачена как ссылка. Захваченная по ссылке переменная действует так же, как любая другая ссылка. При использовании переменной в теле лямбда-выражения фактически применяется объект, с которым связана эта ссылка. В данном случае, когда лямбда-выражение возвращает v1, возвращается значение объекта, на который ссылается переменная v1.
Захват ссылок имеет те же проблемы и ограничения, что и возвращение ссылок (см. раздел 6.1.1). При захвате переменной по ссылке следует быть уверенным, что объект, на который она ссылается, существует на момент выполнения лямбда-выражения. Переменные, захваченные лямбда-выражением, являются локальными, они перестают существовать сразу, как только функция завершится. Если лямбда-выражение продолжит выполняться после завершения функции, то используемые ею локальные переменные окажутся несуществующими.
Иногда захват по ссылке необходим. Например, может понадобиться, чтобы функция biggies() получала ссылку на поток ostream для записи символа, используемого как разделитель:
void biggies(vector<string> &words,
vector<string>::size_type sz,
ostream &os = cout, char c = ' ') {
// код, переупорядочивающий слова как прежде
// оператор вывода количества, пересмотренный для вывода os
for_each(words.begin(), words.end(),
[&os, c](const string &s) { os << s << c; });
}
Объекты потока ostream нельзя копировать (см. раздел 8.1.1); единственный способ захвата объекта os — это ссылка (или указатель).
При передаче лямбда-выражения функции, как и в случае вызова функции for_each(), лямбда-выражение выполняется немедленно. Захват объекта os по ссылке хорош потому, что переменные в функции biggies() существуют во время выполнения функции for_each().
Лямбда-выражение можно также возвратить из функции. Функция может возвратить вызываемый объект непосредственно или возвратить объект класса, у которого вызываемый объект является переменной-членом. Если функция возвращает лямбда-выражение, то по тем же причинам, по которым функция не должна возвращать ссылку на локальную переменную, лямбда-выражение не должно содержать захваченных ссылок.
Когда переменная захвачена по ссылке, следует удостовериться, что эта переменная существует во время выполнения лямбда-выражения.
Совет. Не усложняйте списки захвата лямбда-выраженийМеханизм захвата лямбда-выражения хранит полученную информацию между моментом создания лямбда-выражение (т.е. когда выполняется код определения лямбда-выражения) и моментом собственно выполнения лямбда-выражения. Разработчику следует самостоятельно позаботиться о том, чтобы независимо от момента захвата информации она осталась достоверной на момент выполнения лямбда-выражения.