#опытным
Проследим историю захвата this в лямбду сквозь стандарты С++, там есть на что посмотреть.
С++11
Появились лямбды и в них можно захватывать this как явно, так и неявно через параметры захвата по-умолчанию. Во всех случаях захватывается
&(*this) то есть указатель на текущий объект:struct Foo {
int m_x = 0;
void func() {
int x = 0;
//Explicit capture 'this'
[this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[&]() { /*access m_x and x*/ }();
//Redundant 'this'
[&, this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[=]() { /*access m_x and x*/ }();
//Error
[=, this]() { }();
}
};Однако не было адекватного способа захватить объект по значению aka скопировать его в лямбду.
С++14
Появилась инициализация в захвате, поэтому стал реальным захват объекта по значению:
struct Foo {
int m_x = 0;
void func() {
[copy=*this]() mutable {
copy.m_x++;
}();
}
};В остальном все осталось также.
С++17
Появился захват объекта по значению! Захват по значению может быть очень важно для асинхронных операций, которые откладывают выполнение лямбд:
struct Processor {
//Some state data..
std::future<void> process(/*args*/) {
//Pre-process...
//Do the data processing asynchronously
return
std::async(std::launch::async,
[=](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};
auto caller() {
Processor p;
return p.process(/*args*/);
} В этом коде UB, потому что возвращаем лямбду со ссылкой на несуществующий объект. Поменять это можно захватом объекта по значению:
struct Processor {
std::future<void> process(/*args*/) {
return
std::async(std::launch::async,
[*this](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};В этом случае в лямбде будет храниться копия объекта и все методы будут обращаться к этому скопированному объекту.
С++20
С появлением захвата this по значению стала очень путающей семантика неявного захвата. Что reference capture &, что value capture =, по факту захватывали текущий объект по ссылке. И ничто неявно не захватывало this по значению.
Изначально в 20-м стандарте эту проблему хотели решить, просто запретив неявный захват this в любом случае. Но посидели и поняли, что для ссылочного захвата по умолчанию семантика неявного захвата ссылки на объект(чем отдаленно явняется this) корректна. А вот для захвата по значению - нет.
Поэтому начиная с С++20 мы не можем неявно захватывать this в default capture by value:
struct Bagel {
int x = 0;
void func() {
//OK until C++20. Warning in C++20.
[=]() { std::cout << x; }();
//Error/warning until C++20. OK in C++20.
[=, this]() { std::cout << x; }();
}
};Оставлю в картинке под постом инфографику по изменениям в стандартах относительно захвата this.
Know the history. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20