два раза выведет числа 54, 135, потому что функция, возвращенная после первого обращения к make_offseter, всегда добавляет 42 к переданному ей аргументу Напротив, функция, возвращенная после второго обращения к make_offseter, добавляет к своему аргументу 123. Это самый безопасный вид захвата локальных переменных — все значения копируются, поэтому лямбда-функцию можно вернуть и вызывать вне контекста функции, в которой она была создана. Но это не единственно возможное решение, можно захватывать локальные переменные и по ссылке. В таком случае попытка вызвать лямбда-функцию после того, как переменные, на которые указывают ссылки, были уничтожены в результате выхода из области видимости объемлющей их функции или блока, приведёт к неопределённому поведению, точно так же, как обращение к уничтоженной переменной в любом другом случае.
Лямбда-функция, захватывающая все локальные переменные по ссылке, начинается интродуктором [&]:
int main() {
int offset = 42; ←(1)
std::function
[&](int j){return offset + j;};←(2)
offset = 123; ←(3)
std::function
[&](int j){return offset + j;};←(4)
std::cout <<
offset_a(12) << "," << offset_b(12) << std::endl; ←(5)
offset = 99; ←(6)
std::cout <<
offset_a(12) << "," << offset_b(12) << std::endl; ←(7)
}
Если функция make_offseter из предыдущего примера захватывала копию смещения offset, то функция offset_a в этом примере, начинающаяся интродуктором [&], захватывает offset по ссылке (2). Неважно, что начальное значение offset было равно 42 (1); результат вызова offset_a(12) зависит от текущего значения offset. Значение offset было изменено на 123 (3) перед порождением второй (идентичной) лямбда-функции offset_b (4), но эта вторая функция снова производит захват по ссылке, поэтому результат, как и прежде, зависит от текущего значения offset.
Теперь при печати первой строки (5), offset всё еще равно 123, поэтому печатаются числа 133, 135. Однако к моменту печати второй строки (7) offset стало равно 99 (6), поэтому печатается 111, 111. И offset_a, и offset_b прибавляют текущее значение offset (99) к переданному аргументу (12).
Но ведь это С++, поэтому вам не обязательно выбирать между всем или ничем; вполне можно захватывать одни переменные по значению, а другие по ссылке. Более того, можно даже указывать, какие именно переменные захватить. Нужно лишь изменить лямбда-интродуктор. Если требуется [=], но после знака равенства перечислите переменные, захватываемые по ссылке, предпослав им знаки амперсанда. В следующем примере печатается 1239, потому что переменная i копируется в лямбда-функцию, a j и k захватываются по ссылке:
int main() {
int i=1234, j=5678, k=9;
std::function
i = 1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}
Можно поступить и наоборот — по умолчанию захватывать по ссылке, но некоторое подмножество переменных копировать. В таком случае воспользуйтесь интродуктором [&], а после знака амперсанда перечислите переменные, захватываемые по значению. В следующем примере печатается 5688, потому что i захватывается по ссылке, a j и k копируются:
int main() {
int i=1234, j=5678, k= 9;
std::function
i = 1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}
Если требуется захватить только именованные переменные, то можно опустить знак = или & и просто перечислить захватываемые переменные, предпослав знак амперсанда тем, что должны захватываться по ссылке, а не по значению. В следующем примере печатается 5682, потому что i и k захвачены по ссылке, a j скопирована
int main() {
int i=1234, j=5678, k=9;
std::function
i =1;
j = 2;
k = 3;
std::cout << f() << std::endl;
}