Pointer declarations should be read right to left.
If Fred
is some type, then
• Fred*
is a pointer to a Fred
(the *
is pronounced “pointer to a”).
• const Fred*
is a pointer to a Fred
that cannot be changed via that pointer.
• Fred* const
is a const
pointer to a Fred
. The Fred
object can be changed via the pointer, but the pointer itself cannot be changed.
• const Fred* const
is a const
pointer to a Fred
that cannot be changed via that pointer.
References are similar: read them right to left.
• Fred&
is a reference to a Fred
(the &
is pronounced “reference to a”).
• const Fred&
is a reference to a Fred
that cannot be changed via that reference.
Note that Fred& const
and const Fred& const
are not included in the second list. This is because references are inherently immutable: you can never rebind the reference so that it refers to a different object.
With proper use of the keyword const
, the C++ compiler detects many unexpected changes to objects and flags these violations with error messages at compile time. This is often called const correctness. For example, function f()
uses the const
keyword to restrict itself so that it won't be able to change the caller's string
object:
If f()
changes its passed string
anyway, the compiler flags it as an error at compile time:
In contrast, function g()
declares its intent to change the caller's string
object by its lack of the const
keyword in the appropriate place:
For example, it is legal and appropriate for g()
to modify the caller's string
object:
Also it would be legal and appropriate for g()
to pass its parameter to f()
, since the called function, f()
, is at least as restrictive as the caller, g()
(in this case, the called function is actually more restrictive):
However, it would be illegal for the opposite to occur. That is, if f()
passed its parameter to g()
, the compiler would give an error message since the called function, g()
, is less restrictive than the caller, f()
:
const
imply runtime overhead?No, there is no runtime overhead. All tests for const
ness are done at compile time. Neither runtime space nor speed is degraded.
const
allow the compiler to generate more efficient code?Occasionally, but that's not the purpose of const
. The purpose of const
is correctness, not optimization. That is, const
helps the compiler find bugs, but it does not (normally) help the compiler generate more efficient code.
Declaring the const
ness of a parameter or variable is just another form of type safety; therefore, const
correctness can be considered an extension of C++'s type system. Type safety provides some degree of semantic integrity by promising that, for instance, something declared as a string
cannot be used as an int
. However, const
correctness guarantees even tighter semantic correctness by making sure that data that is not intended to be changed cannot be changed. With const
correctness, it is easier to reason about the correctness of the software. This is helpful during software inspection.
It is almost as if const string
and string
are of different, but related, classes. Because type safety helps produce correct software (especially in large systems and applications), const
correctness is a worthy goal.
const
correctness tedious?It is no more tedious than declaring the type of a variable.
In C++, const
correctness is simply another form of type information. In theory, expressing any type information is unnecessary, given enough programmer discipline and testing. In practice, developers often leave a lot of interesting information about their code in their heads where it cannot be exploited or verified by the compiler. For instance, when programmers write a function such as the following print()
function, they know implicitly that they are passing by reference merely to avoid the overhead of passing by value; there is no intention of changing the string
during the print()
operation.
If this information is documented only by comments in the code or in a separate manual, it is easy for these comments to become inconsistent with the code; the compiler can't read comments or manuals. The most cost-effective way to document this information is with the five-letter word const
:
This form of documentation is succinct, in one place, and can be verified and exploited by the compiler.
const
correctness be done sooner rather than later?Adding constraints later can be difficult and expensive. If a function was not originally restricted with respect to changing a by-reference parameter, adding the restriction (that is, changing a parameter from string&
to const string&
) can cause a ripple through the system. For instance, suppose f()
calls g()
, and g()
calls h()
:
Changing f(string& s)
to f(const string& s)
causes error messages until g(string&)
is changed to g(const string&)
. But this change causes error messages until h(string&)
is changed to h(const string&)
, and so on. The ripple effect is magnificent—and expensive.
The moral is that const
correctness should be installed from the very beginning.
An inspector is a member function that returns information about an object's state without changing the object's abstract state (that is, calling an inspector does not cause an observable change in the behavior of any of the object's member functions). A mutator changes the state of an object in a way that is observable to outsiders: it changes the object's abstract state. Here is an example.
The pop()
member function is a mutator because it changes the Stack
by removing the top element. The numElems()
member function is an inspector because it simply counts the number of elements in the Stack
without making any observable changes to the Stack
. The const
decoration after numElems()
indicates that numElems()
promises not to change the Stack
object.
Only inspectors may be called on a reference-to-const
or pointer-to-const
:
const
? There are two ways to look at it. When looking at the member function from the inside out, the answer is “whenever the member function wants to guarantee that it won't make any observable changes to its this
object.” When looking at the member function from the outside in, the answer is “whenever a caller needs to invoke the member function via a reference-to-const
or pointer-to-const
.” Hopefully these two approaches end up agreeing with each other. If not, then the application may have a serious design flaw, or perhaps it needs to be const overloaded (that is, two member functions with the same name and the same parameters, but one is a const
member function and the other is not).
The compiler won't allow a const
member function to change *this
or to invoke a non-const
member function for this
object:
Although not fleshed out in this example, member functions push()
and pop()
may throw exceptions in certain circumstances. In this case they throw runtime_error
, which is the standard exception class that is thrown for errors that are detectable only at runtime.
const
apply to the object's bitwise state or its abstract state?The const
keyword should refer to the object's abstract state.
The const
modifier is a part of the class's public
: interface; therefore, it means what the designer of the public
: interface wants it to mean. As an interface designer, the most useful strategy is to tie const
to the object's abstract state rather than to its bitwise state. For example, in some circumstances a member function changes its object's bitwise state, yet the change doesn't cause any observable change to any of the object's public
: member functions (that is, the abstract state is not changed). In this case, the member function should still be const
since it never changes the meaning of the object (see FAQ 14.12). It is even more common for a member function to change an object's abstract state even though it doesn't change the object's bitwise state.
For example, the following MyString
class stores its string data on the heap, pointed to by the member datum data_
. (The name bad_alloc
is the standard exception class that is thrown when memory is exhausted, and the name out_of_range
is the standard exception class that is thrown when a parameter is out of range.)
The abstract state of the MyString
object s
is represented by values returned by s[i]
, where i
ranges from 0
to s.size()–1
, inclusive. The bitwise state of a MyString
is represented by the bits of s
itself (that is, by s.len_
and the pointer s.data_
).
Even though s.toUpper()
doesn't change s.len_
or the pointer s.data_
, MyString::toUpper()
is a non-const
member function because it changes the abstract state (the state from the user's perspective). In other words, toUpper()
doesn't change the bitwise state of the object, but it does change the meaning of the object; therefore it is a non-const
member function.
const
not be used in declaring formal parameters?Do not use const
for formal parameter types that are passed by value, because a const
on a pass-by-value parameter affects (constrains) only the code inside the function; it does not affect the caller. For example, replace f(const Fred x)
with either f(const Fred& x)
or f(Fred x)
.
As a special case of this rule, it is inappropriate to use Fred* const
in a formal parameter list. For example, replace f(Fred* const p)
with f(Fred* p)
, and replace g(const Fred* const p)
with g(const Fred* p)
.
Finally, do not use Fred& const
in any context. The construct is nonsensical because a reference can never be rebound to a different object. (See FAQ 14.01.)
const
not be used in declaring a function return type?A function that returns its result by value should generally avoid const
in the return type. For example, replace const Fred f()
with either Fred f()
or const Fred& f()
. Using const Fred f()
can be confusing to users, especially in the idiomatic case of copying the return result into a local.
The exception to this rule is when users apply a const
-overloaded member function directly to the temporary returned from the function. An example follows.
Because f()
returns a non-const Fred
, f().wilma()
invokes the non-const
version of Fred::wilma()
. In contrast, g()
returns a const Fred
, so g().wilma()
invokes the const
version of Fred::wilma()
. Thus, the output of this program is as follows.
f(): Fred::wilma()
g(): Fred::wilma() const
const
member function? Preferably the data member should be declared with the mutable
keyword. If that cannot be done, const_cast
can be used.
A small percentage of inspectors need to make nonobservable changes to data members. For example, an object whose storage is physically located in a database might want to cache its last lookup in hopes of improving the performance of its next lookup. In this case there are two critical issues: (1) if someone changes the database, the cached value must somehow be either changed or marked as invalid (cache coherency); (2) the const
lookup member function needs to make a change to the cached value. In cases like this, changes to the cache are not observable to users of the object (the object does not change its abstract state; see FAQ 14.09).
The easiest way to implement a nonobservable change is to declare the cache using the mutable
keyword. The mutable
keyword tells the compiler that const
member functions are allowed to change the data member.
The second alternative is to cast away the const
ness of the this
pointer using the const_cast
keyword. In the following example, self
is equal to this
(that is, they point to the same object), but self
is a Fred*
rather than a const Fred*
so self
can be used to modify the this
object.
const
reference (pointer) to it?Yes, due to aliasing.
The const
part restricts the reference (pointer); it does not restrict the object. Many programmers erroneously think that the object on the other end of a const
reference (pointer) cannot change. For example, if const int& i
refers to the same int
as int& j
, j
can change the int
even though i
cannot. This is called aliasing, and it can confuse programmers who are unaware of it.
There is no rule in C++ that prohibits this sort of thing. In fact, it is considered a feature of the language that programmers can have several pointers or references refer to the same object (plus it could not be figured out in some cases, e.g., if there are intermediate functions between main()
and sample(const int&
,int&)
and if these functions are defined in different source files and are compiled on different days of the week). The fact that one of those references or pointers is restricted from changing the underlying object is a restriction on the reference (or pointer), not on the object.
const_cast
mean lost optimization opportunities? No, the compiler doesn't lose optimization opportunities because of const_cast
.
Some programmers are afraid to use const_cast
because they're concerned that it will take away the compiler's ability to optimize the code. For example, if the compiler cached data members of an object in registers, then called a const
member function, in theory it would need to reload only those registers that represent mutable
data members. However in practice this kind of optimization cannot occur, with or without const_cast
.
The reason the optimization cannot occur is that it would require the compiler to prove that there are no non-const
references or pointers that point to the object (the aliasing problem; see FAQ 14.13), and in many cases this cannot be proved.