Herb Sutter recently gave a talk about how the
const keyword and the
mutable keyword have subtle but profoundly different semantics in C++ 11. In a nutshell, he says that C++ 11 corrects the wishy-washy definition of
const in C++ 98;
const used to mean “logically constant,” but now it means thread-safe. And
mutable now means thread-safe as well. His summary slide says:
mutable == thread safe (bitwise const or internally synchronized)
Now, I think Herb’s talk is quite informative, and I don’t dispute the core of what he was trying to convey. It’s a good insight, well worth the community’s attention. I learned something important; I recommend that you watch the talk. Using
const well is an essential skill. But I think in his enthusiasm about the way the language has evolved to make semantics clearer, Herb does us a disservice by oversimplifying.
When Herb uses the C++ == operator to boil his point down to a pithy summary, he’s implying true equivalence; what’s on one side of the operator is, for all intents and purposes, identical to or indistinguishable from what’s on the other side. And while
mutable and thread-safe are highly related concepts, they are not equivalent enough to each other for ==.
To understand why, answer the following question: Why would good code use
mutable even if it’s single-threaded?
Ah. I imagine you nodding your head sagely. You see where I’m going, don’t you?
These two keywords don’t just define semantics for cross-thread access; they define the semantics a variable or object supports when accessed by various scopes (e.g., subroutines or code blocks) on the same thread. If you pass a
const Widget & to a function, that function can’t call
Widget::modifyState() even if it’s the only thread in the universe. If you declare a
m_lazy_init member variable to be
mutable, you are telling the compiler to let you change it where it would normally be disallowed, including on the same thread.
const means unchangeable in whatever scope sees const (including many threads), which is why it also implies thread-safe (if all threads see const);
mutable means changing safely in one or many threads, which is why it also implies thread-safe (if all threads see const). In C++ 98, these semantics were a bit loose. You could use them carelessly, cast away parts of their guarantees, and generally operate as a law unto yourself. In C++ 11 the semantics of
mutable are explicit and exacting; the standard library demands thread-safe copy construction. As a result, their role in thread safety is clarified, and we all write better code. Mutexes and atomics and certain kinds of queues are inherently safe to change from any thread; they deserve and require the
Instead of Herb’s final equation, I’d propose a Venn diagram:
- Video: You Don’t Know const and mutable (herbsutter.com)
- Impossible to be const-correct when combining data and its lock? (stackoverflow.com)