Ответ
Ключевое слово 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 (уже уничтоженная) никогда не менялась.
Что происходит на практике:
- При создании лямбды
[call_count]значение переменнойcall_countкопируется в неявный член анонимного класса лямбды. - Без
mutableоператор вызова (operator()) этого класса являетсяconst-методом, поэтому менять члены класса нельзя. - С
mutableоператор вызова становится не-const, и изменять захваченные копии разрешается.
Важные ограничения:
mutableвлияет только на захват по значению. Для захвата по ссылке ([&x]) он не нужен, так как лямбда и так может менять исходный объект.- Это не делает лямбду потокобезопасной. Если лямбда с состоянием вызывается из нескольких потоков, требуется синхронизация.
- Использование
mutableдля изменения захваченных по значению объектов может быть признаком неоптимального дизайна — иногда лучше явно захватить по ссылке или использоватьstd::reference_wrapper.
Ответ 18+ 🔞
А, ну это про mutable в лямбдах! Ёпта, тема-то простая, но народ постоянно путается, как мартышлюшка с гранатой. Смотри, сейчас разжую.
Вот представь: лямбда — это такой маленький зверёк, который может что-то с собой носить (захватывать переменные). Если он носит вещь по значению — то есть делает её свою личную копию — то по умолчанию он с ней как музейный работник: смотреть можно, трогать нельзя, всё под стеклом, константно. А mutable — это как дать этому зверьку разрешение накосячить внутри своего личного пространства. Он свою копию может изменить, но на оригинал, который снаружи, это вообще никак не влияет.
Вот смотри на примере, тут всё ясно станет:
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 (уже уничтоженная) никогда не менялась.
Видишь? Мы создали счётчик. Без mutable эта строка ++call_count; просто не скомпилировалась бы, компилятор сказал бы тебе что-то вроде «чувак, ты чё, это же константная копия, тебе её менять низззя». А с mutable — пожалуйста, инкрементируй свою личную копию сколько влезет. Оригинальная переменная call_count, которая была в функции make_counter, уже давно накрылась медным тазом, когда функция завершилась, ей вообще похуй.
Что на самом деле под капотом:
- Лямбда
[call_count]— это не магия, а просто анонимный класс. Переменнаяcall_countстановится его приватным полем, в которое при создании копируется значение. - Метод
operator()(тот самый, что вызывается приcounter()) по умолчанию —const. А вconst-методе поля менять нельзя. Вот где собака зарыта! - Ключевое слово
mutableубирает этотconstс метода. Всё. Теперь можно менять свои внутренние поля-копии.
Но есть нюансы, бля:
mutableработает ТОЛЬКО на захваченное по значению. Если ты захватил что-то по ссылке ([&x]), то тебе это слово вообще не нужно. Ты и так можешь менять оригинал, через ссылку. Тут уже доверия ебать ноль, можно наворотить дел.- Не думай, что
mutableделает твою лямбду крутой и потокобезопасной. Если этот «зверёк с состоянием» побежит из нескольких потоков — будет тебе хиросима и нагасаки в одном флаконе, data races гарантированы. Нужны мьютексы, атомики — в общем, нормальная синхронизация. - Часто если тебе прям позарез нужно менять захваченную копию — это может быть звоночек, что архитектура кривовата. Может, проще было захватить по ссылке (осторожно, с временем жизни!) или использовать
std::ref. Но бывает и нормально, когда нужно именно инкапсулировать изменяемое состояние внутри замыкания.
Короче, mutable — это просто свисток, который говорит компилятору: «отпусти поводок, я свою игрушку внутри буду менять, на внешний мир это не влияет». Главное — понимать, что меняешь именно копию, а не оригинал, и не лезть с этим в многопоточку без защиты.