Как mutable применяется вместе с лямбда-функцией?

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

Ответ

Ключевое слово mutable у лямбды снимает константность с захваченных по значению переменных, позволяя изменять их копии внутри тела лямбды. Без mutable такие захваченные копии являются const.

Простой пример — счётчик вызовов:

auto make_counter() {
    int call_count = 0; // Локальная переменная
    // Захватываем call_count по значению, но mutable позволяет его менять
    return [call_count]() mutable {
        ++call_count;
        return call_count;
    };
}

auto counter = make_counter();
std::cout << counter(); // 1
std::cout << counter(); // 2
std::cout << counter(); // 3
// Важно: внешняя call_count (уже уничтоженная) никогда не менялась.

Что происходит на практике:

  1. При создании лямбды [call_count] значение переменной call_count копируется в неявный член анонимного класса лямбды.
  2. Без mutable оператор вызова (operator()) этого класса является const-методом, поэтому менять члены класса нельзя.
  3. С mutable оператор вызова становится не-const, и изменять захваченные копии разрешается.

Важные ограничения:

  • mutable влияет только на захват по значению. Для захвата по ссылке ([&x]) он не нужен, так как лямбда и так может менять исходный объект.
  • Это не делает лямбду потокобезопасной. Если лямбда с состоянием вызывается из нескольких потоков, требуется синхронизация.
  • Использование mutable для изменения захваченных по значению объектов может быть признаком неоптимального дизайна — иногда лучше явно захватить по ссылке или использовать std::reference_wrapper.