← Назад к вопросам

Как mutable применяется вместе с lambda?

2.0 Middle🔥 131 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ: mutable позволяет менять переменные, захваченные by value

Ключевое слово mutable в лямбда функциях позволяет изменять захваченные переменные, которые были переданы по значению (by value). По умолчанию, переменные захваченные by value в лямбде const, что запрещает их модификацию.

Проблема без mutable

int counter = 0;

auto increment = [counter] {
    counter++;  // ❌ Ошибка! counter захвачен by value и const
    // error: cannot assign to a variable captured by value
};

increment();

Почему это запрещено?

Лямбда генерируется компилятором как класс:

class LambdaIncrement {
private:
    int counter;  // const int counter
public:
    // Оператор() неявно const
    void operator()() const {
        counter++;  // ❌ Нельзя менять const переменную
    }
};

Оператор вызова operator() неявно const, поэтому нельзя менять члены класса.

Решение: mutable

Вариант 1: mutable лямбда

int counter = 0;

auto increment = [counter]() mutable {
    counter++;  // ✅ Работает!
    std::cout << "Counter: " << counter << std::endl;
};

increment();  // Counter: 1
increment();  // Counter: 2
increment();  // Counter: 3

std::cout << "Original: " << counter << std::endl;  // Original: 0
// Внешняя переменная не изменилась!

Лямбда с mutable генерируется как:

class LambdaIncrement {
private:
    int counter;  // Уже НЕ const
public:
    // Оператор() НЕ const благодаря mutable
    void operator()() {
        counter++;  // ✅ Теперь можно менять
    }
};

Важное замечание: копирование значений

By value означает копирование, поэтому изменение в лямбде не влияет на оригинальную переменную:

int x = 5;

auto print_and_modify = [x]() mutable {
    x = 100;
    std::cout << "Inside lambda: " << x << std::endl;  // 100
};

print_and_modify();
std::cout << "Outside lambda: " << x << std::endl;    // 5 (не изменилось!)

Это отличается от захвата by reference!

Захват by reference (без mutable)

int counter = 0;

auto increment = [&counter] {  // Захват по ссылке
    counter++;  // ✅ Работает без mutable!
    // Изменяется оригинальная переменная
};

increment();
std::cout << counter << std::endl;  // 1 (изменилась!)
increment();
std::cout << counter << std::endl;  // 2

Почему работает без mutable?

Потому что оператор() всё ещё const, но мы не меняем саму ссылку, а изменяем объект, на который она указывает:

class LambdaIncrement {
private:
    int& counter;  // Ссылка const (не меняется)
public:
    void operator()() const {  // const оператор
        counter++;  // ✅ Меняем объект, на который указывает ссылка
    }
};

Практические примеры

Пример 1: Счётчик вызовов

auto call_counter = [count = 0]() mutable {
    return ++count;
};

std::cout << call_counter() << std::endl;  // 1
std::cout << call_counter() << std::endl;  // 2
std::cout << call_counter() << std::endl;  // 3

Пример 2: Состояние в лямбде

struct State {
    int processed = 0;
    int errors = 0;
};

State state;

auto process_item = [state]() mutable {
    if (/* validation fails */) {
        state.errors++;
    } else {
        state.processed++;
    }
    return state.processed + state.errors;
};

// Каждый вызов работает со своей копией state
process_item();
process_item();

Пример 3: Правильное использование с reference

std::vector<int> results;

auto process_data = [&results](int value) {
    // Без mutable! Захват по ссылке
    results.push_back(value * 2);
};

process_data(5);
process_data(10);
process_data(15);

for (auto r : results) {
    std::cout << r << " ";  // 10 20 30
}

Когда использовать что

Используй reference ([&var]) когда:

  • Нужно изменить оригинальную переменную
  • Переменная будет существовать дольше, чем лямбда
  • Нельзя копировать (большой объект, no copy constructor)
int sum = 0;
auto accumulate = [&sum](int x) {
    sum += x;  // Меняем оригинальный sum
};

Используй value ([var]) + mutable когда:

  • Нужно локальное состояние лямбды
  • Изменения не должны влиять на оригинал
  • Функция будет вызвана много раз
auto counter = [n = 0]() mutable {
    return ++n;  // Локальное состояние
};

Используй value без mutable когда:

  • Нужна неизменяемая копия переменной
  • Лямбда не должна менять состояние
auto print_value = [x](){ 
    std::cout << x << std::endl;  // Только читаем x
};

Современный подход (C++17+)

Вместо mutable часто лучше использовать init capture с явным состоянием:

// Старый стиль
auto counter1 = [n = 0]() mutable { return ++n; };

// Новый стиль (с шаблоном)
auto make_counter = [n = 0]() mutable { return ++n; };

// Или как объект с состоянием
class Counter {
    int n = 0;
public:
    int operator()() { return ++n; }
};
Auto counter2 = Counter();

Итог

mutable позволяет менять переменные захваченные by valueBy value + mutable = локальная копия, оригинал не меняется ✅ By reference не требует mutable (но опасна для неживущих переменных) ✅ Правило: по умолчанию захватывай by reference для изменения, by value для безопасности ⚠️ Lifetime: при захвате по ссылке убедись, что переменная живёт дольше лямбды