Question
C++ mutable Keyword Explained: Const Member Functions, Caches, and Mutexes
Question
In C++, what is the purpose of the mutable keyword beyond allowing a data member to be modified inside a const member function?
For example:
class Foo {
private:
mutable bool done_;
public:
void doSomething() const {
// ...
done_ = true;
}
};
My understanding is that mutable allows a member variable to be changed even when the containing object is treated as const.
I have also seen this used with a boost::mutex so that const member functions can still lock the mutex for thread safety. That seems useful, but it also feels a little unusual.
Is this essentially the only role of mutable, or are there other important uses and design reasons behind it?
Short Answer
By the end of this page, you will understand what mutable means in C++, why it exists, and when it is appropriate to use it. You will see that its main job is to allow changes to an object's internal, non-observable state even inside const member functions. Common examples include cached values, lazy computation, counters, and mutexes used for thread safety.
Concept
In C++, mutable is a keyword that can be applied to a non-static data member of a class. It tells the compiler:
"This member is allowed to change even when the object is viewed through a
constinterface."
That is the core purpose of mutable.
Why this exists
A const member function promises not to change the object's logical state from the caller's point of view. However, some parts of an object are often considered implementation details rather than part of its visible value.
Examples:
- a cached result
- a lazily initialized lookup table
- a debug counter
- a mutex used only for synchronization
These do not usually change what the object means; they only support how it works internally.
This leads to an important C++ idea:
Physical constness vs logical constness
- Physical constness: no bits in the object change.
- Logical constness: the externally visible meaning of the object does not change.
const member functions are mainly about logical constness in good design. mutable exists so you can preserve a const API while still changing internal bookkeeping data.
What mutable does not do
mutable does not:
- make the whole object mutable
- allow modifying non-
mutablemembers in aconstfunction - bypass
constfor objects that are truly placed in read-only memory - mean "safe to modify from multiple threads"
It only changes the rule for that specific member.
Common and legitimate uses
1. Caching
You may want a const getter to compute a value once and store it.
class Document {
private:
std::string text_;
mutable bool cached_ = false;
mutable std::size_t wordCount_ = 0;
public:
std::size_t wordCount() const {
if (!cached_) {
// expensive calculation
wordCount_ = text_.size(); // simplified example
cached_ = true;
}
return wordCount_;
}
};
From the caller's perspective, asking for wordCount() does not change the document's meaning.
2. Mutexes in const member functions
This is a very common real-world use.
#include <mutex>
class Counter {
private:
int value_ = 0;
mutable std::mutex mtx_;
public:
int get() const {
std::lock_guard<std::mutex> lock(mtx_);
return value_;
}
};
Locking a mutex protects access, but it does not conceptually change the value of the object being observed. So mutable is appropriate.
3. Instrumentation or statistics
class Service {
private:
mutable int accessCount_ = 0;
public:
int getValue() const {
++accessCount_;
return 42;
}
};
This may be reasonable if the counter is not part of the object's public meaning.
Design intent
So yes: the essential role of mutable is exactly to allow modification of specific data members in const member functions. But the deeper reason is to support logical constness and clean object design.
That is why using mutable for a mutex is not usually a hack. It is one of the standard textbook examples of proper use.
Mental Model
Think of an object as a library book.
- The content of the book is its logical state.
- A sticky note inside the cover is internal bookkeeping.
If a librarian adds a sticky note saying, "last checked on Tuesday," the story in the book has not changed. The book is logically the same book.
In this analogy:
- normal data members are part of the book's content
mutablemembers are the sticky notes used for internal management- a
constmember function means "do not change the meaning of the book"
So mutable says:
"This field is internal housekeeping. It may change without breaking the promise of
const."
Syntax and Examples
The syntax is simple: place mutable before a non-static data member.
class Example {
private:
int value_ = 0;
mutable int cache_ = 0;
};
Basic example
#include <iostream>
class Number {
private:
int value_;
mutable bool inspected_ = false;
public:
Number(int value) : value_(value) {}
int getValue() const {
inspected_ = true; // allowed because inspected_ is mutable
return value_;
}
};
int main() {
const Number n(10);
std::cout << n.getValue() << "\n";
}
What happens here
nis aconst NumbergetValue()is aconstmember function- inside
getValue(),value_cannot be modified - but
inspected_can be modified because it is markedmutable
Cache example
#include <string>
class Message {
private:
std::string text_;
mutable bool cachedLengthReady_ = false;
mutable std::size_t cachedLength_ = 0;
public:
Message(std::string text) : text_(std::move(text)) {}
std::size_t length() const {
if (!cachedLengthReady_) {
cachedLength_ = text_.length();
cachedLengthReady_ = true;
}
return cachedLength_;
}
};
This keeps the public API const while still storing a computed result.
Mutex example
#include <mutex>
class SafeValue {
private:
int value_ = 0;
mutable std::mutex mutex_;
public:
int get() const {
std::lock_guard<std::mutex> lock(mutex_);
return value_;
}
void set(int value) {
std::lock_guard<std::mutex> lock(mutex_);
value_ = value;
}
};
The mutex changes during locking and unlocking, but that change is not part of the object's visible value.
Step by Step Execution
Consider this example:
#include <iostream>
class Temperature {
private:
double celsius_;
mutable bool cacheValid_ = false;
mutable double cachedFahrenheit_ = 0.0;
public:
Temperature(double celsius) : celsius_(celsius) {}
double fahrenheit() const {
if (!cacheValid_) {
cachedFahrenheit_ = celsius_ * 9.0 / 5.0 + 32.0;
cacheValid_ = true;
}
return cachedFahrenheit_;
}
};
int main() {
const Temperature t(25.0);
std::cout << t.fahrenheit() << "\n";
std::cout << t.fahrenheit() << "\n";
}
Step-by-step
First call to t.fahrenheit()
tis aconst Temperatureobject.fahrenheit()is allowed because it is markedconst.- The program checks
cacheValid_. - It is
false, so the Fahrenheit value is calculated. cachedFahrenheit_is updated.cacheValid_is set totrue.- The cached value is returned.
Second call to t.fahrenheit()
fahrenheit()runs again.cacheValid_is nowtrue.- The function skips the calculation.
- It returns
cachedFahrenheit_directly.
Key idea
Even though the object is const, the cache changes internally. That is exactly what mutable is for: internal optimization without changing the logical value of the object.
Real World Use Cases
mutable appears in real C++ code when a class has internal details that may change without changing its public meaning.
Common use cases
Caching expensive calculations
- computing a parsed representation once
- storing the result of a lookup
- memoizing string length, hash, or derived data
Lazy initialization
- creating helper objects only when first needed
- initializing a compiled regex on demand
- building a lookup table the first time a method is called
Thread safety in read-only operations
- locking a mutex inside
constgetters - protecting shared state while preserving a
constAPI
Logging and metrics
- counting accesses
- tracking cache hits and misses
- collecting debug information
Example scenarios
- A
Documentclass caches word count in aconst wordCount()method. - A
Configclass lazily parses a file the first time a getter is called. - A
DatabaseConnectionPooluses amutable std::mutexinconstmethods to safely inspect pool state. - A
Matrixclass caches its determinant after the first calculation.
Real Codebase Usage
In real projects, mutable is usually used sparingly and intentionally.
Common patterns
1. Cached values in getters
Developers often keep the public API read-only while storing computed results internally.
class UserProfile {
private:
std::string rawJson_;
mutable bool parsed_ = false;
mutable std::string displayName_;
public:
std::string displayName() const {
if (!parsed_) {
// parse rawJson_...
displayName_ = "Alice";
parsed_ = true;
}
return displayName_;
}
};
2. mutable mutex for const-correct locking
This is very common and generally considered good practice.
class Store {
private:
std::vector<int> items_;
mutable std::mutex mutex_;
public:
std::size_t size() const {
std::lock_guard<std::mutex> lock(mutex_);
return items_.size();
}
};
3. Guarding logical constness
A good rule is:
- if changing the member would change what users think the object is, do not use
mutable - if it only affects implementation details,
mutablemay be appropriate
Related design patterns
Guard clauses
A const method may check a cache flag early and return quickly.
if (cacheValid_) {
return cachedValue_;
}
Validation
You may lazily validate data and store the result.
Error handling
Be careful with caches and lazy initialization if exceptions can happen halfway through updating mutable members.
Practical advice
Use mutable when it improves const-correct APIs without lying to the caller. If you find yourself marking many important fields as mutable, it may be a design smell.
Common Mistakes
Beginners often misunderstand mutable as a way to "get around" const. That leads to poor design.
Mistake 1: Using mutable to modify real object state
Broken idea:
class BankAccount {
private:
mutable int balance_;
public:
void withdraw(int amount) const {
balance_ -= amount; // very misleading design
}
};
Why this is bad
Withdrawing money clearly changes the logical state of the object. A const function should not do that.
Fix
Make the function non-const.
void withdraw(int amount) {
balance_ -= amount;
}
Mistake 2: Thinking mutable makes code thread-safe
Broken idea:
class Counter {
private:
mutable int count_ = 0;
public:
int get() const {
++count_; // allowed, but not thread-safe
return count_;
}
};
Why this is bad
mutable only changes const rules. It does not add synchronization.
Fix
Use proper synchronization or atomics when needed.
Mistake 3: Overusing mutable
If many members need to be mutable, your class may be mixing read-only and state-changing responsibilities badly.
Mistake 4: Confusing const correctness with bitwise immutability
A const method can still change mutable members, so const does not always mean "no bytes ever change."
Mistake 5: Forgetting cache invalidation
Broken example:
class Item {
private:
std::string name_;
mutable bool cached_ = false;
mutable std::size_t len_ = 0;
public:
std::size_t length() const {
if (!cached_) {
len_ = name_.size();
cached_ = true;
}
return len_;
}
void setName(const std::string& name) {
name_ = name;
// forgot to reset cached_
}
};
Fix
Reset the cache whenever the source data changes.
void setName(const std::string& name) {
name_ = name;
cached_ = false;
}
Comparisons
Here is how mutable compares to related ideas.
| Concept | What it does | Typical use | Changes logical state? |
|---|---|---|---|
const member function | Promises not to modify the object's logical state | getters, read-only operations | No |
mutable member | Allows one specific member to change in const functions | caches, mutexes, counters | Usually no |
non-const member function | Freely modifies object state | setters, updates, mutations | Yes |
const_cast | Removes constness manually | rare low-level cases | Risky |
mutable vs const_cast
mutable is the safe, intended language feature for specific members.
class Example {
private:
mutable int x_ = 0;
public:
void f() const { x_ = 1; }
};
Using const_cast instead is usually worse design:
class Example {
private:
int x_ = 0;
public:
void f() const {
const_cast<Example*>(this)->x_ = 1; // avoid this pattern
}
};
Logical constness vs physical constness
| Idea | Meaning |
|---|---|
| Physical constness | No member data changes at all |
| Logical constness | No externally meaningful state changes |
mutable exists to support logical constness.
Cheat Sheet
mutable Type member;
Rules
mutableapplies to non-static data members.- It allows that member to be modified inside
constmember functions. - It does not make other members modifiable.
- It is mainly used for logical constness.
Good uses
- caches
- lazy initialization
- mutexes for synchronization in
constmethods - debug counters or metrics
Bad uses
- changing true business state in a
constmethod - hiding poor class design
- assuming it provides thread safety
Example
class Example {
private:
int value_ = 0;
mutable std::mutex mtx_;
public:
int get() const {
std::lock_guard<std::mutex> lock(mtx_);
return value_;
}
};
Quick test
Ask yourself:
If this member changes, does the object's meaning change for the caller?
- If yes, do not use
mutable. - If no,
mutablemay be appropriate.
FAQ
What does mutable mean in C++?
It means a specific non-static data member can still be modified even when the object is accessed through a const member function.
Is mutable only used with const member functions?
That is its main purpose. It matters when an object is treated as const, especially inside const member functions.
Is using mutable for a mutex good practice?
Yes, usually. Locking is considered internal synchronization, not a change to the object's logical value.
Does mutable break const-correctness?
Not if used properly. It supports logical constness by allowing internal implementation details to change.
Can mutable be used for caching?
Yes. Caching is one of the most common and appropriate uses.
Does mutable make code thread-safe?
No. It only changes const rules. You still need mutexes, atomics, or other synchronization tools.
Should I use mutable often?
Usually no. It is useful, but it should be used sparingly for clearly internal state.
Is mutable better than const_cast for this purpose?
Yes. mutable is the intended and safer language feature for members that may change in const operations.
Mini Project
Description
Build a small C++ class that stores a piece of text and exposes const methods to inspect it safely and efficiently. The project demonstrates two common uses of mutable: caching a computed value and locking a mutex inside a const method.
Goal
Create a thread-safe TextStats class with a const method that returns a cached word count.
Requirements
- Create a class that stores a text string.
- Add a
constmethod that returns the number of words. - Cache the computed word count so repeated calls do not recalculate it.
- Use a mutex to protect access to shared state.
- Keep the public read operation
const.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.