C++11 подарил нам лямбда-функции и, вместе с ними, еще один способ неявного получения висячих ссылок.
Лямбда-функция, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой ее создали. Как только мы куда-то возвращаем или сохраняем лямбду, начинается веселье:
auto make_add_n(int n) {
return [&](int x) {
return x + n; // n — станет висячей ссылкой!
};
}
...
auto add5 = make_add_n(5);
std::cout << add5(5); // UB!
Ничего принципиально нового — тут все те же проблемы, что и с возвратом ссылки из функции. clang иногда способен выдать предупреждение.
Но стоит нам принять аргумент make_add_n
по ссылке — и никаких предупреждений не будет.
Аналогично проблему можно наиграть и для методов объектов:
struct Task {
int id;
std::function<void()> GetNotifier() {
return [this]{
// this — может стать висячей ссылкой!
std::cout << "notify " << id << "\n";
};
}
};
int main() {
auto notify = Task { 5 }.GetNotifier();
notify(); // UB!
}
Но в этом примере можно заметить this
в списке захвата и насторожиться. До C++20 же можно отстрелить ногу чуть менее явно:
struct Task {
int id;
std::function<void()> GetNotifier() {
return [=]{
// this — может стать висячей ссылкой!
std::cout << "notify " << id << "\n";
};
}
};
=
предписывает захватывать все по значению, но захватывается не поле id
, а сам указатель this
.
Если видите лямбду, в списке захвата которой есть this
, =
(до С++20) или &
,
обязательно проверьте, как и где эта лямбда используется. Добавьте перегрузки проверки времени жизни захватываемых переменных.
struct Task {
int id;
std::function<void()> GetNotifier() && = delete;
std::function<void()> GetNotifier() & {
return [this]{
// для this теперь намного сложнее стать висячей ссылкой
std::cout << "notify " << id << "\n";
};
}
};
Если возможно, вместо захвата по ссылке, лучше использовать захват по значению или захват с инициализацией перемещением.
auto make_greeting(std::string msg) {
return [message = std::move(msg)] (const std::string& name) {
std::cout << message << name << "\n";
};
}
...
auto greeting = make_greeting("hello, ");
greeting("world");