Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ: 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 value ✅ By value + mutable = локальная копия, оригинал не меняется ✅ By reference не требует mutable (но опасна для неживущих переменных) ✅ Правило: по умолчанию захватывай by reference для изменения, by value для безопасности ⚠️ Lifetime: при захвате по ссылке убедись, что переменная живёт дольше лямбды