Const Qualifier

A keyword or other means for asserting that a type (or a reference to a type) is (or ought to be) immutable, and/or an object method will not modify the state of the object.

There are two strategies, in a C++ program, for using the ConstQualifier. One is to practice ConstCorrectness; the other is to AvoidConstCompletely. In general, any program can use a library which is ConstCorrect? with minimal issues; however, a const correct program will have difficulty importing a library which practices AvoidConstCompletely. The C++ standard library is const-correct (though early versions of the C standard library are not--one of the biggest uses of CastingAwayConst is to call harmless functions like strcmp() on older C/C++ implementations, and get it to compile without warnings). As a result, AvoidConstCompletely is probably best done in application code.

Sounds simple enough, but it gets VERY tricky..... and that's ignoring things like PhysicalConstness vs LogicalConstness.

Programmers of FunctionalProgrammingLanguages can start chuckling now, because most of the following doesn't apply to them. In a language without SideEffects, everything is immutable, and the ConstQualifier doesn't apply.

In languages (or expressions) that use the ValueModel, const is fairly straightforward. Once an object is constructed, it's state doesn't change. For AtomicObjects (those which don't contain references to other objects), there is little controversy. C++ optimizes heavily based on const for ValueObjects. Declaring something to be "const int foo=5" is as good as defining a macro or an enum (the old C way of declaring manifest constants).

If you use the ReferenceModel? for variables (or use pointers in a language such as C/C++), things start to get interesting. For example: In C/C++, by use of a truly horrible syntax, you can achieve both.

 foo *x = &someFoo;

is a pointer to type foo. Both the pointer and the referent can change.

 const foo *x = &someFoo;

is a pointer to type const foo. The pointer can change; however you cannot modify the foo through this pointer. (But wait, see below...)

 foo *const x = &someFoo;

is a const pointer to type foo. The pointer cannot change but the object being pointed to can. And...

 const foo *const x = &someFoo;

is a const pointer to type const foo. Neither pointer nor referent is allowed to change.

Confused yet? So am I. I can remember the first two; I had to look the other two up. (Usually I use typedef when I need the last two cases....)

This is why any C++ textbook worth it's salt will teach you to read types right-to-left; "const foo *const x" reads as "(x) [is a] (const) (pointer) [to a] (foo) [which is] const". -- FauxFaux?

Java has a keyword which resembles const, called "final". When applied to objects, it prevents the pointer (please don't harangue me about JavaHasNoPointers?) from changing; but the state of the object being referenced can change all you want. Java also has reserved the keyword const. I'm not sure if that's for future expansion or to prevent it from being used ever. (Java also reserves goto, but it has been stated on numerous occasions that this was done to ensure goto never gets added to the language...) (It is also worth noting that, much like "static" in C/C++, the "final" keyword in Java is horrendously overloaded...)

Don't go away! I've only started.

One common mistake that C/C++ users make is that if a pointer (or reference in C++) is declared const (here meaning the referent cannot change, not the pointer itself) that the underlying object won't change. Wrong. All that means is that the object cannot change through that pointer or reference. The following is perfectly legal.

 Foo *f = new Foo(5);
 const Foo *g = f;
 printf ("%d\n", g.getMyValue());  // a const method
 printf ("%d\n", g.getMyValue());

The output is (of course) 5 followed by 6. The const object g has changed. Since it is perfectly valid to have const and non-const references aliased to the same thing, one cannot assume that a const object won't change. Other than simple types (such as ints and strings), there is no notion of an ImmutableObject in C/C++. const-ness is much weaker than immutability. (And this is without CastingAwayConst -- if we do that then everything flies out the window.) C/C++ even allow the keywords const and volatile to be combined; and it makes perfect sense to do so! (In other words, I can't change the object, but someone else likely will; and do so asynchronous to my operation).

(Some special purpose compilers, such as the C compilers that target commercial DSP processors, do sometimes assume that const = immutable, and implore the user not to alias const arrays. The latest C standard now has a restrict keyword which makes the same promise).

But things get even MORE confusing.

const is bad enough for atomic objects (those which contain no references to other objects; especially references to objects that might be shared). But what about:

 struct const_node {
	void *element;
	struct const_node *next;

(No snickering, LispHippies).

What does it mean to have a const struct const_node*? That the two pointers therein cannot change? Or that the things being pointed to (and their transitive closure) cannot be changed? In C/C++, it is the former; const-ness is a rather shallow concept. (Which means if the logical state of an object depends on objects that it points to; even a const operation can change that state!)

When you start dealing with containers (intelligent ones, not dumb ones like C/C++ arrays), things get even worse. There are (potentially) three levels of constness (assuming the things being contained are AtomicObjects; things get even worse if not). Or combinations of the above. One of the ugliest objects of the C++ StandardTemplateLibrary is the ConstIterator?. Suppose I have a vector of type const vector<T *> (which means the first two bullets above, but NOT the third). Now I need an iterator for such a creature. What is the type of the iterator? Ugh.

-- engineer_scotty (Scott Johnson)

Although const_iterator can be confusing, your explanation of const_iterator is a tad overcomplicated. Start with V, a vector of T (not T*) to avoid confusing the issue with pointers. A const_iterator for such a container does not allow you to modify the objects of type T contained in V. No iterator allows you to insert or delete things in a container; iterators are for accessing individual elements, not the container itself.


  std::vector<int> vint(1, 3);  // init with one element, value "3".
  std::vector<int>::iterator i = vint.begin();
  *i = 5;  // OK
  std::vector<int>::const_iterator ci = vint.begin();
  *ci = 6;  // Error!

That's all there is to it, basically. Derferencing a const iterator of vi yields a int const, which you can't modify. And you can't insert or delete elements from vi using just one of the iterators.

Now if T happens to be of pointer type, e.g. vector<A*>, a const iterator prevents you from modifying the pointers in the container. They're what the container contains, after all -- no conceptual leap necessary. What might be a little surprising is that the things pointed to can be modified. But you discussed this aspect of pointer constness quite well earlier. Dereferencing a const iterator of vector<A*> yields a A* const -- the pointer is const, the pointed-to thing is not.

-- DanMuller

Related to UseConstMemberFunctions.

View edit of January 9, 2008 or FindPage with title or text search