Ключевое слово, которое часто упускают из виду, но оно критически важно для элегантного кода!
Что такое mutable?
Позволяет изменять член данных константного объекта, не нарушая его логическую неизменяемость.
class Example {
public:
void setValue(int val) const {
value = val; // Работает благодаря mutable!
}
private:
mutable int value;
};Четыре основных сценария использования:
1️⃣ Кэширование
Сохраняйте результаты дорогостоящих вычислений:
class DataProcessor {
public:
int getSum() const {
if (!cachedSum_) {
cachedSum_ = std::accumulate(data_.begin(),
data_.end(), 0);
}
return *cachedSum_;
}
private:
std::vector<int> data_;
mutable std::optional<int> cachedSum_;
};Преимущество: Вычисляем один раз, используем многократно!
2️⃣ Отложенные вычисления (Lazy Evaluation)
class LazyString {
public:
const std::string& getUpperCase() const {
if (!upperCase_) {
upperCase_ = original_;
std::transform(upperCase_->begin(),
upperCase_->end(),
upperCase_->begin(),
::toupper);
}
return *upperCase_;
}
private:
std::string original_;
mutable std::optional<std::string> upperCase_;
};Экономия: Вычисляем только когда действительно нужно!
3️⃣ Потокобезопасность
class ThreadSafeCounter {
public:
int getValue() const {
std::lock_guard<std::mutex> lock(mutex_);
return value_;
}
private:
int value_ = 0;
mutable std::mutex mutex_; // Можем блокировать в const методах
};4️⃣ Логическая неизменяемость
class BigData {
public:
int get(size_t index) const {
++accessCount_; // Не меняет логическое состояние
return data_[index];
}
size_t getAccessCount() const {
return accessCount_;
}
private:
std::vector<int> data_;
mutable size_t accessCount_ = 0;
};💡 Практический пример: отложенная загрузка конфигурации
class Configuration {
public:
Configuration(const std::string& filename)
: filename_(filename) {}
std::string getValue(const std::string& key) const {
loadConfigIfNeeded(); // Загружаем только при первом обращении
return config_[key];
}
private:
void loadConfigIfNeeded() const {
if (!isLoaded_) {
// Загружаем конфигурацию из файла
isLoaded_ = true;
}
}
std::string filename_;
mutable bool isLoaded_ = false;
mutable std::unordered_map<std::string, std::string> config_;
};Использование:
const Configuration config("settings.conf");
// Файл загрузится только здесь ↓
std::cout << config.getValue("log_level");❗️Потенциальные ошибки:
1. Потокобезопасность
// ❌ НЕ потокобезопасно!
mutable int count_ = 0;
void increment() const { ++count_; }
// ✅ Потокобезопасно
mutable int count_ = 0;
mutable std::mutex mutex_;
void increment() const {
std::lock_guard<std::mutex> lock(mutex_);
++count_;
}
2. Не нарушайте логическую неизменяемость
// ❌ Плохо - меняет логическое состояние
class Bad {
void setData(int d) const { data_ = d; }
mutable int data_;
};
// ✅ Хорошо - только внутренняя оптимизация
class Good {
int getData() const {
++accessCount_; // Метаданные, не логика
return data_;
}
int data_;
mutable size_t accessCount_ = 0;
};
3. Документируйте использование
class Example {
private:
// mutable используется для кэширования
// с целью повышения производительности
mutable std::optional<int> cachedValue_;
};Итог ⬇️
mutable — мощный инструмент для:
✅ Кэширования результатов
✅ Отложенных вычислений
✅ Потокобезопасности в const методах
✅ Сохранения логической неизменяемости
📎 Статья
🎙 Новости
📝 База вопросов
