Расскажи про способы захвата переменных в лямбда-выражениях C++

«Расскажи про способы захвата переменных в лямбда-выражениях C++» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В C++ лямбда-выражения могут захватывать переменные из окружающего контекста несколькими способами, что определяет время жизни и доступ к этим переменным.

Основные способы захвата:

  1. Захват по значению [=] Создается копия переменной на момент создания лямбды. Изменения внутри лямбды не затрагивают оригинал.

    int x = 42;
    auto lambda = [=]() { 
        // x - локальная копия со значением 42
        return x + 1; // Возвращает 43, но x остаётся 42
    };
  2. Захват по ссылке [&] Лямбда работает с оригинальной переменной. Опасно, если лямбда переживёт захваченную переменную.

    int y = 10;
    auto lambda = [&]() { 
        y++; // Модифицирует оригинальную переменную y
    };
    lambda(); // Теперь y = 11
  3. Явный (смешанный) захват [var1, &var2] Позволяет точно контролировать способ захвата для каждой переменной.

    int a = 1, b = 2;
    auto lambda = [a, &b]() { 
        return a + b; // a - по значению, b - по ссылке
    };
  4. Инициализирующий захват [name = expr] (C++14) Позволяет создавать новые члены лямбды, инициализируемые произвольным выражением. Особенно полезен для перемещения (std::move) или захвата unique_ptr.

    std::unique_ptr<Resource> resource = std::make_unique<Resource>();
    auto lambda = [capturedResource = std::move(resource)]() {
        capturedResource->use(); // Захвачен по перемещению
    };
    // resource теперь nullptr
  5. *Захват указателя this и `this(C++17)** [this]захватывает указатель на текущий объект по ссылке.[*this]` захватывает копию текущего объекта по значению, что безопаснее при асинхронных операциях.

    class Processor {
        int m_data;
    public:
        auto getLambda() {
            // Захват копии объекта, а не указателя
            return [*this]() { return m_data; };
        }
    };

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