[18] Const correctness
(Part of C++ FAQ Lite, Copyright © 1991-2000, Marshall Cline, cline@parashift.com)


FAQs in section [18]:


[18.1] What is "const correctness"? UPDATED!

[Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]

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...

In the pass by reference-to-const and pass by pointer-to-const cases, any attempts to change to 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, if 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...

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);  
// OK 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().

TopBottomPrevious sectionNext section ]


[18.2] How is "const correctness" related to ordinary type safety? UPDATED!

[Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]

Declaring the const-ness of a parameter is just another form of type safety. It is almost as if a const std::string, for example, is a different class than an ordinary std::string, since the const variant is missing the various mutative operations in the non-const variant (e.g., you can imagine that a const std::string simply doesn't have an assignment operator).

If you find ordinary type safety helps you get systems correct (it does; especially in large systems), you'll find const correctness helps also.

TopBottomPrevious sectionNext section ]


[18.3] 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."

TopBottomPrevious sectionNext section ]


[18.4] What does "const Fred* p" mean?

It means p points to an object of class Fred, but p can't be used to change that Fred object (naturally p could also be NULL).

For example, if class Fred has a const member function called inspect(), saying p->inspect() is OK. But if class Fred has a non-const member function called mutate(), saying p->mutate() is an error (the error is caught by the compiler; no run-time tests are done, which means const doesn't slow your program down).

TopBottomPrevious sectionNext section ]


[18.5] What's the difference between "const Fred* p", "Fred* const p" and "const Fred* const p"?

You have to read pointer declarations right-to-left.

TopBottomPrevious sectionNext section ]


[18.6] What does "const Fred& x" mean?

It means x aliases a Fred object, but x can't be used to change that Fred object.

For example, if class Fred has a const member function called inspect(), saying x.inspect() is OK. But if class Fred has a non-const member function called mutate(), saying x.mutate() is an error (the error is caught by the compiler; no run-time tests are done, which means const doesn't slow your program down).

TopBottomPrevious sectionNext section ]


[18.7] Does "Fred& const x" make any sense?

No, it is nonsense.

To find out what the above declaration means, you have to read it right-to-left. Thus "Fred& const x" means "x is a const reference to a Fred". But that is redundant, since references are always const. You can't reseat a reference. Never. With or without the const.

In other words, "Fred& const x" is functionally equivalent to "Fred& x". Since you're gaining nothing by adding the const after the &, you shouldn't add it since it will confuse people. I.e., the const will make some people think that the Fred is const, as if you had said "const Fred& x".

TopBottomPrevious sectionNext section ]


[18.8] What does "Fred const& x" mean?

"Fred const& x" is functionally equivalent to "const Fred& x".

The problem with using "Fred const& x" (with the const before the &) is that it could easily be mis-typed as the nonsensical "Fred &const x" (with the const after the &).

Better to simply use const Fred& x.

TopBottomPrevious sectionNext section ]


[18.9] What is a "const member function"? UPDATED!

[Recently removed a spurious ")" thanks to Stan Brown (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]

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();   
// OK: doesn't change a changeable object
   changeable.mutate();    
// OK: changes a changeable object
 
   unchangeable.inspect(); 
// OK: doesn't change an unchangeable object
   unchangeable.mutate();  
// ERROR: attempt to change unchangeable object
 }

The error in unchangeable.mutate() is caught at compile time. There is no runtime space or speed penalty for const.

The trailing const on inspect() member function means that the abstract (client-visible) state of the object isn't going to change. This is slightly different from promising that the "raw bits" of the object's struct aren't going to change. 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 promises only that the object won't change via that pointer.

TopBottomPrevious sectionNext section ]


[18.10] What do I do if I want to update an "invisible" data member inside a const member function? UPDATED!

[Recently added a warning against use of const_cast on const objects thanks to TiTi (on 3/00). Click here to go to the next FAQ in the "chain" of recent changes.]

Use mutable (or, as a last resort, use const_cast).

A small percentage of inspectors need to make innocuous changes to data members (e.g., a Set object might want to cache its last lookup in hopes of improving the performance of its next lookup). By saying the changes are "innocuous," I mean that the changes wouldn't be visible from outside the object's interface (otherwise the member function would be a mutator rather than an inspector).

When this happens, the data member which will be modified should be marked as mutable (put the mutable keyword just before the data member's declaration; i.e., in the same place where you could put const). This tells the compiler that the data member is allowed to change during a const member function. If your compiler doesn't support the mutable keyword, you can cast away the const'ness of this via the const_cast keyword (but see the NOTE below before doing this). E.g., in Set::lookup() const, you might say,

 Set* self = const_cast<Set*>(this);
   
// See the NOTE below before doing this!

After this line, self will have the same bits as this (e.g., self == this), but self is a Set* rather than a const Set*. Therefore 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 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 and tell me that version X of compiler Y on machine Z allows you to 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.

TopBottomPrevious sectionNext section ]


[18.11] 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 inlined, and when the constructor itself is inlined, and when any member functions the constructor calls are inline).

TopBottomPrevious sectionNext section ]


[18.12] Why does the compiler allow me to change an int after I've pointed at it with a const int*? UPDATED!

[Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]

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;
   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".

TopBottomPrevious sectionNext section ]


[18.13] 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;   
// const member function
   void mutate();          
// A non-const member function
 };
 
 int main()
 {
   Fred f;
   const Fred* p = &f;
         Fred* q = &f;
 
   p->inspect();    
// OK: No change to *p
   p->mutate();     
// Error: Can't change *p via p
 
   q->inspect();    
// OK: q is allowed to inspect the object
   q->mutate();     
// OK: q is allowed to mutate the object
 
   f.inspect();     
// OK: f is allowed to inspect the object
   f.mutate();      
// OK: f is allowed to mutate the object
 }

TopBottomPrevious sectionNext section ]


E-Mail E-mail the author
C++ FAQ LiteTable of contentsSubject indexAbout the author©Download your own copy ]
Revised Jul 10, 2000