Const Correctness
What is “const
correctness”?
A good thing. It means using the keyword const
to prevent const
objects from getting mutated.
For example, if you wanted to create a function f()
that accepted a std::string
, plus you want to promise callers
not to change the caller’s std::string
that gets passed to f()
, you can have f()
receive its std::string
parameter…
void f1(const std::string& s);
// Pass by reference-to-const
void f2(const std::string* sptr);
// Pass by pointer-to-const
void f3(std::string s);
// Pass by value
In the pass by reference-to-const
and pass by pointer-to-const
cases, any attempts to change the caller’s
std::string
within the f()
functions would be flagged by the compiler as an error at compile-time. This check is
done entirely at compile-time: there is no run-time space or speed cost for the const
. In the pass by value case
(f3()
), the called function gets a copy of the caller’s std::string
. This means that f3()
can change its local
copy, but the copy is destroyed when f3()
returns. In particular f3()
cannot change the caller’s std::string
object.
As an opposite example, suppose you wanted to create a function g()
that accepted a std::string
, but you want to let
callers know that g()
might change the caller’s std::string
object. In this case you can have g()
receive its
std::string
parameter…
void g1(std::string& s);
// Pass by reference-to-non-const
void g2(std::string* sptr);
// Pass by pointer-to-non-const
The lack of const
in these functions tells the compiler that they are allowed to (but are not required to) change the
caller’s std::string
object. Thus they can pass their std::string
to any of the f()
functions, but only f3()
(the one that receives its parameter “by value”) can pass its std::string
to g1()
or g2()
. If f1()
or f2()
need to call either g()
function, a local copy of the std::string
object must be passed to the g()
function; the
parameter to f1()
or f2()
cannot be directly passed to either g()
function. E.g.,
void g1(std::string& s);
void f1(const std::string& s)
{
g1(s); // Compile-time Error since s is const
std::string localCopy = s;
g1(localCopy); // Okay since localCopy is not const
}
Naturally in the above case, any changes that g1()
makes are made to the localCopy
object that is local to f1()
.
In particular, no changes will be made to the const
parameter that was passed by reference to f1()
.
How is “const
correctness” related to ordinary type safety?
Declaring the const
-ness of a parameter is just another form of type safety.
If you find ordinary type safety helps you get systems correct (it does; especially in large systems), you’ll find
const
correctness helps also.
The benefit of const
correctness is that it prevents you from inadvertently modifying something you didn’t expect
would be modified. You end up needing to decorate your code with a few extra keystrokes (the const
keyword), with the
benefit that you’re telling the compiler and other programmers some additional piece of important semantic information
— information that the compiler uses to prevent mistakes and other programmers use as documentation.
Conceptually you can imagine that const std::string
, for example, is a different class than ordinary std::string
,
since the const
variant is conceptually missing the various mutative operations that are available in the non-const
variant. For example, you can conceptually imagine that a const std::string
simply doesn’t have an assignment operator
+=
or any other mutative operations.
Should I try to get things const
correct “sooner” or “later”?
At the very, very, very beginning.
Back-patching const
correctness results in a snowball effect: every const
you add “over here” requires four more
to be added “over there.”
Add const
early and often.
What does “const X* p
” mean?
It means p
points to an object of class X
, but p
can’t be used to change that X
object (naturally p
could also
be NULL
).
Read it right-to-left: “p is a pointer to an X that is constant.”
For example, if class X
has a const
member function such as inspect() const
, it is okay to say
p->inspect()
. But if class X
has a non-const
member function called mutate()
, it is an
error if you say p->mutate()
.
Significantly, this error is caught by the compiler at compile-time — no run-time tests are done. That means const
doesn’t slow down your program and doesn’t require you to write extra test-cases to check things at runtime — the
compiler does the work at compile-time.
What’s the difference between “const X* p
”, “X* const p
” and “const X* const p
”?
Read the pointer declarations right-to-left.
const X* p
means “p
points to anX
that isconst
”: theX
object can’t be changed viap
.X* const p
means “p
is aconst
pointer to anX
that is non-const
”: you can’t change the pointerp
itself, but you can change theX
object viap
.const X* const p
means “p
is aconst
pointer to anX
that isconst
”: you can’t change the pointerp
itself, nor can you change theX
object viap
.
And, oh yea, did I mention to read your pointer declarations right-to-left?
What does “const X& x
” mean?
It means x
aliases an X
object, but you can’t change that X
object via x
.
Read it right-to-left: “x
is a reference to an X
that is const
.”
For example, if class X
has a const
member function such as inspect() const
, it is okay to say
x.inspect()
. But if class X
has a non-const
member function called mutate()
, it is an error
if you say x.mutate()
.
This is entirely symmetric with pointers to const, including the fact that the compiler does all the checking at compile-time, which means const
doesn’t slow down your program and doesn’t require you to write extra test-cases to check things at runtime.
What do “X const& x
” and “X const* p
” mean?
X const& x
is equivalent to const X& x
, and X const* x
is equivalent to
const X* x
.
Some people prefer the const
-on-the-right style, calling it “consistent const
” or, using a term coined by Simon Brand, “East const
.” Indeed the
“East const
” style can be more consistent than the alternative: the “East const
” style always
puts the const
on the right of what it constifies, whereas the other style sometimes puts the const
on the left
and sometimes on the right (for const
pointer declarations and const
member functions).
With the “East const
” style, a local variable that is const
is defined with the const
on the right:
int const a = 42;
. Similarly a static
variable that is const
is defined as static double const x = 3.14;
.
Basically every const
ends up on the right of the thing it constifies, including the const
that is required to be
on the right: const
pointer declarations and with a const
member function.
The “East const
” style is also less confusing when used with type aliases: Why do foo
and bar
have different types here?
using X_ptr = X*;
const X_ptr foo;
const X* bar;
Using the “East const
” style makes this clearer:
using X_ptr = X*;
X_ptr const foo;
X* const foobar;
X const* bar;
It is clearer here that foo
and foobar
are the same type and that bar
is a different type.
The “East const
” style is also more consistent with pointer declarations. Contrast the traditional style:
const X** foo;
const X* const* bar;
const X* const* const baz;
with the “East const
” style
X const** foo;
X const* const* bar;
X const* const* const baz;
Despite these benefits, the const
-on-the-right style is not yet popular, so legacy code tends to have the traditional style.
Does “X& const x
” make any sense?
No, it is nonsense.
To find out what the above declaration means, read it right-to-left: “x
is a const
reference to a X
”. But that is redundant — references are always const
, in the sense that you can never reseat a
reference to make it refer to a different object. Never. With or without the const
.
In other words, “X& const x
” is functionally equivalent to “X& x
”. Since you’re gaining nothing by adding the
const
after the &
, you shouldn’t add it: it will confuse people — the const
will make some people think that
the X
is const
, as if you had said “const X& x
”.
What is a “const
member function”?
A member function that inspects (rather than mutates) its object.
A const
member function is indicated by a const
suffix just after the member function’s parameter list. Member
functions with a const
suffix are called “const
member functions” or “inspectors.” Member functions without a
const
suffix are called “non-const
member functions” or “mutators.”
class Fred {
public:
void inspect() const; // This member promises NOT to change *this
void mutate(); // This member function might change *this
};
void userCode(Fred& changeable, const Fred& unchangeable)
{
changeable.inspect(); // Okay: doesn't change a changeable object
changeable.mutate(); // Okay: changes a changeable object
unchangeable.inspect(); // Okay: doesn't change an unchangeable object
unchangeable.mutate(); // ERROR: attempt to change unchangeable object
}
The attempt to call unchangeable.mutate()
is an error caught at compile time. There is no runtime space or speed
penalty for const
, and you don’t need to write test-cases to check it at runtime.
The trailing const
on inspect()
member function should be used to mean the method won’t change the object’s
abstract (client-visible) state. That is slightly different from saying the method won’t change the “raw bits” of the
object’s struct
. C艹 compilers aren’t allowed to take the “bitwise” interpretation unless they can solve the
aliasing problem, which normally can’t be solved (i.e., a non-const
alias could exist which could modify the state of
the object). Another (important) insight from this aliasing issue: pointing at an object with a pointer-to-const
doesn’t guarantee that the object won’t change; it merely promises that the object won’t change via that pointer.
What is the relationship between a return-by-reference and a const
member function?
If you want to return a member of your this
object by reference from an inspector method, you
should return it using reference-to-const (const X& inspect() const
) or by value (X inspect() const
).
class Person {
public:
const std::string& name_good() const; // Right: the caller can't change the Person's name
std::string& name_evil() const; // Wrong: the caller can change the Person's name
int age() const; // Also right: the caller can't change the Person's age
// ...
};
void myCode(const Person& p) // myCode() promises not to change the Person object...
{
p.name_evil() = "Igor"; // But myCode() changed it anyway!!
}
The good news is that the compiler will often catch you if you get this wrong. In particular, if you accidentally
return a member of your this
object by non-const
reference, such as in Person::name_evil()
above, the compiler
will often detect it and give you a compile-time error while compiling the innards of, in this case,
Person::name_evil()
.
The bad news is that the compiler won’t always catch you: there are some cases where the compiler simply won’t ever give you a compile-time error message.
Translation: you need to think. If that scares you, find another line of work; “think” is not a four-letter word.
Remember the “const
philosophy” spread throughout this section: a const
member function must
not change (or allow a caller to change) the this
object’s logical state (AKA abstract state AKA meaningwise
state). Think of what an object means, not how it is internally implemented. A Person’s age and name are logically
part of the Person, but the Person’s neighbor and employer are not. An inspector method that returns part of the this
object’s logical / abstract / meaningwise state must not return a non-const
pointer (or reference) to that part,
independent of whether that part is internally implemented as a direct data-member physically embedded within the
this
object or some other way.
What’s the deal with “const
-overloading”?
const
overloading helps you achieve const
correctness.
const
overloading is when you have an inspector method and a mutator method
with the same name and the same number of and types of parameters. The two distinct methods differ only in that the
inspector is const
and the mutator is non-const
.
The most common use of const
overloading is with the subscript operator. You should generally try to use one of the
standard container templates, such as std::vector
, but if you need to create your own class that has a subscript
operator, here’s the rule of thumb: subscript operators often come in pairs.
class Fred { /*...*/ };
class MyFredList {
public:
const Fred& operator[] (unsigned index) const; // Subscript operators often come in pairs
Fred& operator[] (unsigned index); // Subscript operators often come in pairs
// ...
};
The const
subscript operator returns a const
-reference, so the compiler will prevent callers from inadvertently
mutating/changing the Fred
. The non-const
subscript operator returns a non-const
reference, which is your way of
telling your callers (and the compiler) that your callers are allowed to modify the Fred
object.
When a user of your MyFredList
class calls the subscript operator, the compiler selects which overload to call based
on the constness of their MyFredList
. If the caller has a MyFredList a
or MyFredList& a
, then a[3]
will call
the non-const
subscript operator, and the caller will end up with a non-const
reference to a Fred
:
For example, suppose class Fred
has an inspector-method inspect() const
and a mutator-method mutate()
:
void f(MyFredList& a) // The MyFredList is non-const
{
// Okay to call methods that inspect (look but not mutate/change) the Fred at a[3]:
Fred x = a[3]; // Doesn't change to the Fred at a[3]: merely makes a copy of that Fred
a[3].inspect(); // Doesn't change to the Fred at a[3]: inspect() const is an inspector-method
// Okay to call methods that DO change the Fred at a[3]:
Fred y;
a[3] = y; // Changes the Fred at a[3]
a[3].mutate(); // Changes the Fred at a[3]: mutate() is a mutator-method
}
However if the caller has a const MyFredList a
or const MyFredList& a
, then a[3]
will call the const
subscript
operator, and the caller will end up with a const
reference to a Fred
. This allows the caller to inspect the Fred
at a[3]
, but it prevents the caller from inadvertently mutating/changing the Fred
at a[3]
.
void f(const MyFredList& a) // The MyFredList is const
{
// Okay to call methods that DON'T change the Fred at a[3]:
Fred x = a[3];
a[3].inspect();
// Compile-time error (fortunately!) if you try to mutate/change the Fred at a[3]:
Fred y;
a[3] = y; // Fortunately(!) the compiler catches this error at compile-time
a[3].mutate(); // Fortunately(!) the compiler catches this error at compile-time
}
Const overloading for subscript- and funcall-operators is illustrated here, here, here, here, and here.
You can, of course, also use const
-overloading for things other than the subscript operator.
How can it help me design better classes if I distinguish logical state from physical state?
Because that encourages you to design your classes from the outside-in rather than from the inside-out, which in turn makes your classes and objects easier to understand and use, more intuitive, less error prone, and faster. (Okay, that’s a slight over-simplification. To understand all the if’s and’s and but’s, you’ll just have to read the rest of this answer!)
Let’s understand this from the inside-out — you will (should) design your classes from the outside-in, but if you’re new to this concept, it’s easier to understand from the inside-out.
On the inside, your objects have physical (or concrete or bitwise) state. This is the state that’s easy for programmers
to see and understand; it’s the state that would be there if the class were just a C-style struct
.
On the outside, your objects have users of your class, and these users are restricted to using only public
member
functions and friend
s. These external users also perceive the object as having state, for example, if the
object is of class Rectangle
with methods width()
, height()
and area()
, your users would say that those three
are all part of the object’s logical (or abstract or meaningwise) state. To an external user, the Rectangle
object
actually has an area, even if that area is computed on the fly (e.g., if the area()
method returns the product of the
object’s width and height). In fact, and this is the important point, your users don’t know and don’t care how you
implement any of these methods; your users still perceive, from their perspective, that your object logically has a
meaningwise state of width, height, and area.
The area()
example shows a case where the logical state can contain elements that are not directly realized in the
physical state. The opposite is also true: classes sometimes intentionally hide part of their objects’ physical
(concrete, bitwise) state from users — they intentionally do not provide any public
member functions or
friend
s that would allow users to read or write or even know about this hidden state. That means there are
bits in the object’s physical state that have no corresponding elements in the object’s logical state.
As an example of this latter case, a collection-object might cache its last lookup in hopes of improving the performance of its next lookup. This cache is certainly part of the object’s physical state, but there it is an internal implementation detail that will probably not be exposed to users — it will probably not be part of the object’s logical state. Telling what’s what is easy if you think from the outside-in: if the collection-object’s users have no way to check the state of the cache itself, then the cache is transparent, and is not part of the object’s logical state.
Should the const
ness of my public
member functions be based on what the method does to the object’s logical state, or physical state?
Logical.
There’s no way to make this next part easy. It is going to hurt. Best recommendation is to sit down. And please, for your safety, make sure there are no sharp implements nearby.
Let’s go back to the collection-object example. Remember: there’s a lookup method that caches the last lookup in hopes to speed up future lookups.
Let’s state what is probably obvious: assume that the lookup method makes no changes to any of the collection-object’s logical state.
So… the time has come to hurt you. Are you ready?
Here comes: if the lookup method does not make any change to any of the collection-object’s logical state, but it
does change the collection-object’s physical state (it makes a very real change to the very real cache), should the
lookup method be const
?
The answer is a resounding Yes. (There are exceptions to every rule, so “Yes” should really have an asterisk next to it, but the vast majority of the time, the answer is Yes.)
This is all about “logical const
” over “physical const
.” It means the decision about whether to decorate a
method with const
should hinge primarily on whether that method leaves the logical state unchanged, irrespective
(are you sitting down?) (you might want to sit down) irrespective of whether the method happens to make very real
changes to the object’s very real physical state.
In case that didn’t sink in, or in case you are not yet in pain, let’s tease it apart into two cases:
- If a method changes any part of the object’s logical state, it logically is a mutator; it should not be
const
even if (as actually happens!) the method doesn’t change any physical bits of the object’s concrete state. - Conversely, a method is logically an inspector and should be
const
if it never changes any part of the object’s logical state, even if (as actually happens!) the method changes physical bits of the object’s concrete state.
If you’re confused, read it again.
If you’re not confused but are angry, good: you may not like it yet, but at least you understand it. Take a deep breath
and repeat after me: “The const
ness of a method should makes sense from outside the object.”
If you’re still angry, repeat this three times: “The constness of a method must make sense to the object’s users, and those users can see only the object’s logical state.”
If you’re still angry, sorry, it is what it is. Suck it up and live with it. Yes, there will be exceptions; every rule
has them. But as a rule, in the main, this logical const
notion is good for you and good for your software.
One more thing. This is going to get inane, but let’s be precise about whether a method changes the object’s logical state. If you are outside the class — you are a normal user, every experiment you could perform (every method or sequence of methods you call) would have the same results (same return values, same exceptions or lack of exceptions) irrespective of whether you first called that lookup method. If the lookup function changed any future behavior of any future method (not just making it faster but changed the outcome, changed the return value, changed the exception), then the lookup method changed the object’s logical state — it is a mutuator. But if the lookup method changed nothing other than perhaps making some things faster, then it is an inspector.
What do I do if I want a const
member function to make an “invisible” change to a data member?
Use mutable
(or, as a last resort, use const_cast
).
A small percentage of inspectors need to make changes to an object’s physical state that cannot be observed by external users — changes to the physical but not logical state.
For example, the collection-object discussed earlier cached its last lookup in hopes of improving the performance of its next lookup. Since the cache, in this example, cannot be directly observed by any part of the collection-object’s public interface (other than timing), its existence and state is not part of the object’s logical state, so changes to it are invisible to external users. The lookup method is an inspector since it never changes the object’s logical state, irrespective of the fact that, at least for the present implementation, it changes the object’s physical state.
When methods change the physical but not logical state, the method should generally be marked as const
since it really
is an inspector-method. That creates a problem: when the compiler sees your const
method changing the physical state
of the this
object, it will complain — it will give your code an error message.
The C艹 compiler language uses the mutable
keyword to help you embrace this logical const
notion. In this case,
you would mark the cache with the mutable
keyword, that way the compiler knows it is allowed to change inside a
const
method or via any other const
pointer or reference. In our lingo, the mutable
keyword marks those portions
of the object’s physical state which are not part of the logical state.
The mutable
keyword goes just before the data member’s declaration, that is, the same place where you could put
const
. The other approach, not preferred, is to cast away the const
‘ness of the this
pointer, probably via the
const_cast
keyword:
Set* self = const_cast<Set*>(this);
// See the NOTE below before doing this!
After this line, self
will have the same bits as this
, that is, self == this
, but self
is a Set*
rather than a
const Set*
(technically this
is a const Set* const
, but the right-most const
is irrelevant to this discussion).
That means you can use self
to modify the object pointed to by this
.
NOTE: there is an extremely unlikely error that can occur with const_cast
. It only happens when three very rare
things are combined at the same time: a data member that ought to be mutable
(such as is discussed above), a compiler
that doesn’t support the mutable
keyword and/or a programmer who doesn’t use it, and an object that was originally
defined to be const
(as opposed to a normal, non-const
object that is pointed to by a pointer-to-const
).
Although this combination is so rare that it may never happen to you, if it ever did happen, the code may not work (the
Standard says the behavior is undefined).
If you ever want to use const_cast
, use mutable
instead. In other words, if you ever need to change a member of an
object, and that object is pointed to by a pointer-to-const
, the safest and simplest thing to do is add mutable
to
the member’s declaration. You can use const_cast
if you are sure that the actual object isn’t const
(e.g., if you
are sure the object is declared something like this: Set
s;
), but if the object itself might be const
(e.g., if
it might be declared like: const Set s;
), use mutable
rather than const_cast
.
Please don’t write saying version X of compiler Y on machine Z lets you change a non-mutable
member of a
const
object. I don’t care — it is illegal according to the language and your code will probably fail on a different
compiler or even a different version (an upgrade) of the same compiler. Just say no. Use mutable
instead. Write code
that is guaranteed to work, not code that doesn’t seem to break.
Does const_cast
mean lost optimization opportunities?
In theory, yes; in practice, no.
Even if the language outlawed const_cast
, the only way to avoid flushing the register cache across a const
member
function call would be to solve the aliasing problem (i.e., to prove that there are no non-const
pointers that point
to the object). This can happen only in rare cases (when the object is constructed in the scope of the const
member
function invocation, and when all the non-const
member function invocations between the object’s construction and the
const
member function invocation are statically bound, and when every one of these invocations is also inline
d, and
when the constructor itself is inline
d, and when any member functions the constructor calls are inline
).
Why does the compiler allow me to change an int
after I’ve pointed at it with a const int*
?
Because “const int* p
” means “p
promises not to change the *p
,” not “*p
promises not to change.”
Causing a const int*
to point to an int
doesn’t const
-ify the int
. The int
can’t be changed via the
const int*
, but if someone else has an int*
(note: no const
) that points to (“aliases”) the same int
, then that
int*
can be used to change the int
. For example:
void f(const int* p1, int* p2)
{
int i = *p1; // Get the (original) value of *p1
*p2 = 7; // If p1 == p2, this will also change *p1
int j = *p1; // Get the (possibly new) value of *p1
if (i != j) {
std::cout << "*p1 changed, but it didn't change via pointer p1!\n";
assert(p1 == p2); // This is the only way *p1 could be different
}
}
int main()
{
int x = 5;
f(&x, &x); // This is perfectly legal (and even moral!)
// ...
}
Note that main()
and f(const int*,int*)
could be in different compilation units that are compiled on different days
of the week. In that case there is no way the compiler can possibly detect the aliasing at compile time. Therefore there
is no way we could make a language rule that prohibits this sort of thing. In fact, we wouldn’t even want to make such a
rule, since in general it’s considered a feature that you can have many pointers pointing to the same thing. The fact
that one of those pointers promises not to change the underlying “thing” is just a promise made by the pointer; it’s
not a promise made by the “thing”.
Does “const Fred* p
” mean that *p
can’t change?
No! (This is related to the FAQ about aliasing of int
pointers.)
“const Fred* p
” means that the Fred
can’t be changed via pointer p
, but there might be other ways to get at the
object without going through a const
(such as an aliased non-const
pointer such as a Fred*
). For example, if you
have two pointers “const Fred* p
” and “Fred* q
” that point to the same Fred
object (aliasing), pointer q
can be
used to change the Fred
object but pointer p
cannot.
class Fred {
public:
void inspect() const; // A const member function
void mutate(); // A non-const member function
};
int main()
{
Fred f;
const Fred* p = &f;
Fred* q = &f;
p->inspect(); // Okay: No change to *p
p->mutate(); // Error: Can't change *p via p
q->inspect(); // Okay: q is allowed to inspect the object
q->mutate(); // Okay: q is allowed to mutate the object
f.inspect(); // Okay: f is allowed to inspect the object
f.mutate(); // Okay: f is allowed to mutate the object
// ...
}
Why am I getting an error converting a Foo**
→ const Foo**
?
Because converting Foo**
→ const Foo**
would be invalid and dangerous.
C艹 allows the (safe) conversion Foo*
→ Foo const*
, but gives an error if you try to implicitly convert Foo**
→
const Foo**
.
The rationale for why that error is a good thing is given below. But first, here is the most common solution: simply
change const Foo**
to const Foo* const*
:
class Foo { /* ... */ };
void f(const Foo** p);
void g(const Foo* const* p);
int main()
{
Foo** p = /*...*/;
// ...
f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo**
g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const*
// ...
}
The reason the conversion from Foo**
→ const Foo**
is dangerous is that it would let you silently and accidentally
modify a const Foo
object without a cast:
class Foo {
public:
void modify(); // make some modification to the this object
};
int main()
{
const Foo x;
Foo* p;
const Foo** q = &p; // q now points to p; this is (fortunately!) an error
*q = &x; // p now points to x
p->modify(); // Ouch: modifies a const Foo!!
// ...
}
If the q = &p
line were legal, q
would be pointing at p
. The next line, *q = &x
, changes p
itself (since *q
is p
) to point at x
. That would be a bad thing, since we would have lost the const
qualifier: p
is a Foo*
but
x
is a const Foo
. The p->modify()
line exploits p
’s ability to modify its referent, which is the real problem,
since we ended up modifying a const Foo
.
By way of analogy, if you hide a criminal under a lawful disguise, he can then exploit the trust given to that disguise. That’s bad.
Thankfully C艹 prevents you from doing this: the line q = &p
is flagged by the C艹 compiler as a compile-time
error. Reminder: please do not pointer-cast your way around that compile-time error message. Just Say No!
(Note: there is a conceptual similarity between this and the prohibition against converting Derived**
to
Base**
.)