chitay-knigi.com » Разная литература » C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 31 32 33 34 35 36 37 38 39 ... 121
Перейти на страницу:
Оно означает, что мы берем функцию, принимающую некоторые параметры, а затем сохраняем ее в другом объекте функции, принимающем меньше параметров. В этом случае мы сохраняем функцию plus и принимаем только один параметр, который будет передан в функцию plus. Другой параметр имеет значение 10; его мы сохраняем в объекте функции. Таким образом, мы получаем функцию и назовем ее plus_ten, поскольку она может добавить значение 10 к единственному принимаемому ею параметру.

  auto plus_ten ( [=] (int x) { return plus(10, x);});

    std::cout << plus_ten(5) << 'n';

}

12. Перед компиляцией и запуском программы пройдем по коду еще раз и попробуем предугадать, какие именно значения выведем в терминале. Затем запустим программу и взглянем на реальные выходные данные:

1, 2

3

ab 3

1, 2, 3, 4, 5,

Value of a after 3 incrementer() calls: 3

15

Как это работает

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

Итак, сначала рассмотрим все особенности, связанные с лямбда-выражениями (рис. 4.1).

Как правило, можно опустить большую часть этих параметров, чтобы сэкономить немного времени. Самым коротким лямбда-выражением является выражение []{}. Оно не принимает никаких параметров, ничего не захватывает и, по сути, ничего не делает.

Что же значит остальная часть?

Список для захвата

Определяет, что именно мы захватываем и выполняем ли захват вообще. Есть несколько способов сделать это. Рассмотрим два «ленивых» варианта.

1. Если мы напишем [=] () {...}, то захватим каждую внешнюю переменную, на которую ссылается замыкание, по значению; т.е. эти значения будут скопированы.

2. Запись [&] () {...} означает следующее: все внешние объекты, на которые ссылается замыкание, захватываются только по ссылке, что не приводит к копированию.

Конечно, можно установить настройки захвата для каждой переменной отдельно. Запись [a, &b] () {...} означает, что переменную a мы захватываем по значению, а переменную b — по ссылке. Для этого потребуется напечатать больше текста, но, как правило, данный способ безопаснее, поскольку мы не можем случайно захватить что-то ненужное из-за пределов замыкания.

В текущем примере мы определили лямбда-выражение следующим образом: [count=0] () {...}. В этом особом случае мы не захватываем никаких переменных из-за пределов замыкания, только определили новую переменную с именем count. Тип данной переменной определяется на основе значения, которым мы ее инициализировали, а именно 0, так что она имеет тип int.

Кроме того, можно захватить одни переменные по значению, а другие — по ссылке, например:

□ [a, &b] () {...} — копируем a и берем ссылку на b;

□ [&, a] () {...} — копируем a и применяем ссылку на любую другую переданную переменную;

□ [=, &b, i{22}, this] () {...} — получаем ссылку на b, копируем значение this, инициализируем новую переменную i значением 22 и копируем любую другую использованную переменную.

 

 Если вы попытаетесь захватить переменную-член некоторого объекта, то не сможете сделать это с помощью конструкции [member_a] () {...}. Вместо этого нужно определить либо this, либо *this.

mutable (необязательный)

Если объект функции должен иметь возможность модифицировать получаемые им переменные путем копирования ([=]), то его следует определить как mutable. Это же касается вызова неконстантных методов захваченных объектов.

constexpr (необязательный)

Если мы явно пометим лямбда-выражение с помощью ключевого слова constexpr, то компилятор сгенерирует ошибку, когда это выражение не будет соответствовать критериям функции constexpr. Преимущество использования функций constexpr и лямбда-выражений заключается в том, что компилятор может оценить их результат во время компиляции, если они вызываются с параметрами, постоянными на протяжении данного процесса. Это приведет к тому, что позднее в бинарном файле будет меньше кода.

Если мы не указываем явно, что лямбда-выражения являются constexpr, но эти выражения соответствуют всем требуемым критериям, то они все равно будут считаться constexpr, только неявно. Если нужно, чтобы лямбда-выражение было constexpr, то лучше явно задавать его таковым, поскольку иначе в случае наших неверных действий компилятор начнет генерировать ошибки.

exception attr (необязательный)

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

return type (необязательный)

При необходимости иметь полный контроль над возвращаемым типом, вероятно, не нужно, чтобы компилятор определял его автоматически. В таких случаях можно просто использовать конструкцию [] () -> Foo {}, которая укажет компилятору, что мы всегда будем возвращать объекты типа Foo. 

Добавляем полиморфизм путем оборачивания лямбда-выражений в std::function

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

Для реализации задачи можно поместить несколько объектов функции-наблюдателя в вектор, все они будут принимать в качестве параметра переменную типа int, которая представляет наблюдаемое значение. Мы не знаем, что именно станут делать данные функции при вызове, но нам это и неинтересно.

Какой тип будут иметь объекты функций, помещенные в вектор? Нам подойдет тип std::vector<void (*)(int)>, если мы захватываем указатели на функции, имеющие сигнатуры наподобие void f(int);. Данный тип сработает с любым лямбда-выражением, которое захватывает нечто, имеющее совершенно другой тип в сравнении с обычной функцией, поскольку это не просто указатель на функцию, а объект, объединяющий некий объем данных с функцией! Подумайте о временах до появления С++11, когда лямбда-выражений не существовало. Классы и структуры были естественным способом связывания данных с функциями, и при изменении типов членов класса получится совершенно другой класс. Это естественно, что вектор не может хранить значения разных типов, используя одно имя типа.

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

1 ... 31 32 33 34 35 36 37 38 39 ... 121
Перейти на страницу:

Комментарии
Минимальная длина комментария - 25 символов.
Комментариев еще нет. Будьте первым.
Правообладателям Политика конфиденциальности