TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.36K subscribers
Post #785 4.67K
​​Висячие ссылки в лямбдах
#новичкам

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

В C++11 появились лямбда-выражения, а вместе с ними ещё один способ прострелить себе причинное место.

Лямбда, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой её создали. Как только лямбда покинула скоуп - можно начинать молиться:

auto make_add_n(int n) {
return [&](int x) {
return x + n; // n will become dangling reference!
};
}

auto add5 = make_add_n(5);
std::cout << add5(5) << std::endl; // UB!


Гцц и шланг пишут разный результат на консоль, что напрямую говорит об ub. Можете посмотреть тут. На варнинги об этой ситуации лучше не надеяться, потому что гцц например думает, что в коде все в порядке.

Еще более интересная ситуация с объектами и методами.

struct Task {
int id;

std::function<void()> GetNotifier() {
return [=]{
std::cout << "notify " << id << std::endl;
};
}
};

int main() {
auto notify = Task { 5 }.GetNotifier();
notify();
}


Что же здесь может провиснуть? Никаких локальных объектов в методе GetNotifier нет.

На самом деле провиснет сам объект, на котором вызывается GetNotifier. Мы его аккуратненько и довольно неявненько захватили через копию указателя this. До С++ 20 мы могли захватывать this вот так по значению и такую проблему будет очень сложно дебагать. Ситуация чуть улучшилась в С++20, мы теперь обязаны указывать this в списке захвата:

struct Task {
int id;

std::function<void()> GetNotifier() {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};


Так уже чуть проще отловить проблему.

Как это лечить? Если у вас объект класса провисает, то тут поможет только профилактика и рефакторинг.

В случае с захватом this профилактикой может быть синтаксическое ограничение использование методов, возвращающий лямбду, с помощью ref-квалификаторов методов:

struct Task {
int id;
std::function<void()> GetNotifier() && = delete; // forbit call on temporaries

std::function<void()> GetNotifier() & {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};


Теперь вы не сможете вызвать этот метод на временном объекте, потому что удалена соответствующая перегрузка.

Конечно это вряд ли поможет в многопоточке, но это уже что-то.

Refer to actual things. Stay cool.

#cppcore #cpp11 #cpp20
  • 🔥 28
  • 👍 19
  • ❤ 11
  • 🤯 1
More from @grokaemcpp
  1. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  2. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  3. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  4. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  5. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
  6. Sep 28, 2026​​Stacktrace #опытным Одна из проблема исключений - непонятно, откуда оно прилетело. Ну да…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →