Const Correctness

EditHints:

I think the qualifier page has its own merits. It should contain more about actual use of the qualifier. This page on the other hand should be more about the correctness issues.


C++ provides the const keyword so you can make data, parameters, pointers and member functions immutable. The aim is to protect encapsulation. In C++ you do want to do this - the compiler will save your arse many many times over if you do. But you need to do it up front - doing it after the fact involves a great deal of tail-chasing and pain.


The problem with const in interfaces is that it forces you to make a long ranging decision, that may or may not be valid, and will be painful to change. An alternative would be to use UnitTests voraciously and ponder how so many languages have survived so long without things like const methods.

Const is like a built-in UnitTest. -- JeremyCromwell

Many languages survived long without inheritance too. Shall we discard that?

The decision about whether your caller may change an object you return to it is always made when you define the operation. The decision about whether you will modify an argument to you or merely use it is also made up front. The decision about whether an operation is a mutator or a query is also made up front. The only question that remains is whether you should explicitly declare those decisions or not. As for me, I know my answer to that question.

The decision about whether a value is a constant or a variable is usually obvious too.

Debate about whether const is necessary is analogous to debate about whether declaring the type of a parameter is necessary. Of course it's not absolutely necessary. Neither are high level languages necessary, but I ain't going back to assembler.

Of course not, but you might switch to a dynamically typed high-level language.


I used to ignore const as just way more trouble than it's worth. Then I joined a project where it was standard. I cursed at it for a day or two ... and then found it was catching errors. Sure, use UnitTests voraciously, but const is useful. And if you're using StlStyle you're probably going to be forced to use it anyway. And then, of course, compiler restrictions make const contagious.

On the interface decisions, you're going to make them some time and as described on ExtremeGuidelinesForCeePlusPlus if you're going to try XP then you'll want 'em up front. In other words, Spike 'em. (SpikeSolution)

On other languages that lack const ... well, they're other languages, aren't they? When working in C++ it doesn't pay to go against the GrainOfTheLanguage. -- PeterMerel

Actually, I think that you'd like to make interface decisions over and over again. There isn't much extreme about burning them in. I think that the guidelines there need to be revisited piece by piece. -- MichaelFeathers


I wonder if people who like const are also people who like statically typed languages and people who dislike it are proponents of dynamically typed languages? Personally, I agree with Peter. If you are going to code in C++ and then it pays to get as much benefit from the language and the compiler as possible. To me, const is a benefit because it allows the compiler to find problems for me. -- JamesCrawford

Hmm. C++ has its uses. So does a ScriptingLanguage and odd beasts like Java. I like 'em all for what they're good for. -- pm

const and type may sit together in a declaration, but const is fundamentally different from type. const permits the creation of artificial literals (named constants). const also makes using the address or a string literal or the reference of a numeric literal safe(r). This solves a problem that perhaps other languages don't have (or have solved differently). -- JeremyCromwell


See UseConstMemberFunctions for another discussion of this topic.


Can someone please tell me a story about a bug that they found using const which was so sneaky that they wouldn't have found it if they were writing UnitTests consistently, and such a whopper that finding it was worth making the design less changeable? -- MichaelFeathers

You reduce problems that methods breaking the LawOfDemeter (or whatever) will change your internal state if you're doing something like

 class Foo
 {
 public:
     ...
     Bar const &GetBar() { return m_Bar; }
     ...
 };

By the way const Bar & == Bar const & != Bar & const. The const modifies the thing to its left, if there is a thing to its left.

If you returned a non-const reference, you can change the internals of m_Bar without the Foo instance knowing. This can cause all sorts of havoc, especially in a multi-threaded environment.

But, why are you returning a reference, you ask?

Because I don't have a copy constructor. Or the copy constructor is expensive. Or the copy constructor has side effects. Or m_Bar has a particular resource it's sharing with the Foo through a pointer. Or any number of fun reasons that makes C++ so wildly delicious and so frustratingly error prone.

-- SunirShah

Returning references to private member variables is like exposing one's private parts. Returning const references is slightly better, you allow to see it, but don't allow to touch it. Anyway it can be dangerous because you are exposing the internal structure of the class. For example, you cannot remove the member variable and replace it with a computed value, if you return a reference (or a pointer) to the member variable - even when you return const* or const&. Other objection to returning pointers to/references of member variables is the life cycle of the member. The caller must not use the pointer when the called object has been deleted.

Sometimes, however, it can be useful. In such cases I'd prefer returning const*, because it is more obvious that the returned value is a pointer.

-- GregorRayman

I think Michael is missing the point. const isn't about finding bugs, it's about documenting design. Another programmer can look at the code and tell at a glance, ah good, my object isn't going to get mangled if I invoke this method / pass it as this parameter. -- JohnCarter

Exactly! const is about defining the contract. It's about how you promise to treat an argument, or who owns a returned value. It's about being explicit and careful. const is "restrictive" only in the best sense of that word. const "restricts" your callers to what you already expected of them. const "restricts" you to what you promise. Promises are Good Things. Promises make the world livable.

Actually, const does restricts the callers only. The implementing class can circumvent the const promise in many ways: brutally by casting, properly with the mutable keyword, obscurely by using pointers to internal data like in:

 void doSomething() const {
   this->m_pInternalData->m_iCount++;
 }


I cannot really see that const is restricting the design any more than not using const. Whether a function is an observer or mutator should be part of the design, and specifying this just makes implicit assumptions explicit. A common objection is the cases where a function is logically const (and probably started out as physical const), and then has to change the this pointer (e.g. caching for efficiency). But in these cases, the MutableKeyword? is the perfect solution. (All comments IMHO). -- JohannesBrodwall

Unfortunately, C++ has const methods, but not mutable methods. This means that a (probably private) set method for a cached value (i.e. physical state) will be declared const, or else I have to do a const_cast on '*this'. -- DaveWhipp

A private set method which updates a cached value (only) is logically const (it does not affect observable state) so it's only right that it should be declared const, and make changes to mutable data members only. -- JamesDennett


You can think of const as a type operator: a thing which creates new types from existing ones by removing all the messages not marked "const". If you are working in a statically checked language which doesn't have const, for example Java or Eiffel, you can simulate const by creating the interface by hand. For example:

    class Point : implements Point.Const {
        static interface Const {
             int getX();
             int getY();
        }
        private int x;
        private int y;
        public int getX() { return x; }
        public int getY() { return y; }
        public void setX( int x ) { this.x = x; }
        public void setY( int y ) { this.y = y; }
    }

So const is a special case of the idea that you sometimes want to restrict certain clients to a subset of an interface, and const correctness is just a kind of static type safety.

You can also do it in a dynamically checked language, but you'll probably need to use delegation rather than inheritance. A UnitTest might use a ConstProxy? to ensure that tested code did not change anything it shouldn't.


Use const or die.

... doing it after the fact involves a great deal of tail-chasing and pain.

Not really. It's pretty easy, actually, because the compiler will tell you what to do next. It's just like TestFirstDesign, or calling a method before it exists in Smalltalk. You run the code/compiler, get an error, fix the error, run the code again. Repeat until done.

Sure, if you think you're so smart you can ignore the standard idioms, you'll die, but that's what got you into this mess in the first place. -- SunirShah

I've had to retrospectively const correctify a set of classes before. It was a pain, but I found that each time the compiler winged about a missing const somewhere, it nearly always highlighted a design flaw in the code. Const allows an extra level of strictness in your design and implimentation. When done in situe it keeps your design on track and exact.-- Mike Ainsworth


[Comments (including mine ;-) moved to CppBashing.]


Doesn't using const rigorously make the intent of the code more clear? I find it very helpful to know that a method will not change the parameters or its object without having to read the entire source of the method. I also do not believe it causes a problem with long range decision making. If the intent of the method changes away from what was prescribed by the const declarations, I think it is very questionable whether you are using the same function or overlaying a new operation on an existing name (the latter is bad). I personally like const because it makes me think about what I am about to do on a very precise level before I start to do it. -- WayneMack

P.S. I have not really ever had const catch any errors, I just find it is a good tool for thinking about the the method I am about ready to create or change.


Can someone comment on the use of 'const' as a hint to the compiler and on the effect this can have on optimization? How would this affect the java example given above? -- JoelCrisp?

In C++, const doesn't much effect optimization, at least for complex objects like the Java one. The reason is that it usually indicates logical immutability, not physical, and physical is what the optimizer is interested in.

In a case like:

    const Point *p = new Point();
the object's constructor could have saved a non-const reference to the object, which could change the object's state if ever the flow of control goes out of the compiler's sight. And if the flow of control never goes out of the compiler's sight, the compiler can figure out for itself whether the object is changed.

There may be an exception when the constness refers to an object rather than a variable, so that the object (eg) could be placed in read-only memory. In some cases, along the lines of:

    const int size = 10;
the compiler will avoid allocating memory for "size" at all. But maybe a good compiler could do this for ordinary objects if the same conditions obtain, eg (a) static linkage (const implies static in C++), (b) constructor and destructor code inline or otherwise known (trivial for primitive types); (c) object simple enough to be worth analyzing. So even here const may be just a hint rather than giving the compiler new exploitable knowledge. -- DaveHarris

I just started the process of converting a small application (~14k lines) to ConstCorrectness; and with GCC 3.3, it seems to have improved performance remarkably... from 154 seconds down to 108 seconds. Is const likely to improve performance like this, or could this improvement be from somewhere else? -- JevonWright?


Here are two more votes for const.

Scenario #1

When applied to reference and pointer parameters const is a huge help. For example:

    Foo f( 1234, "Some text" );

b.Check( f );

f.Something();

When you look at this simple code snippet, can you tell if the call on object b changes the object f?

From this code alone, no. No you can't. A maintainer must locate the declaration of Check(). If Check() is declared like this: Check( Foo& ), then reading Check()'s declaration still won't tell you whether it modifies f. A maintainer must then locate the definition of Check() and dredge through it to answer this simple question. If the object b is of a polymorphic type, you may have a number of Check() method definitions to locate and study.

If Check() had been declared like this: Check( const Foo& ), then a maintainer would not need to see Check()'s definition/s. The declaration would suffice.

But I still need to figure out which role the parameter plays in the Check call. For that I'll read the parameter name, the comments or the definition of Check. Once I've done this, whether the argument is modified or not had better be self-evident. In other words, asking if Check modifies the argument is rarely an interesting question by itself. -- AndersMunch

I agree with AndersMunch that I still need to find out the role of each parameter of Check() before I can write a correct call to Check(), and then it is self-evident whether the author of Check intended to modify any parameters. But imagine that the programmer of some method intended (and noted in the comments) that the method/function never modifies the argument. Then imagine that some bug in that program ( writing "if( f.number = 1234 ){...}" when I meant to use == ). If I used const, then the compiler immediately notifies me of the problem. Otherwise I don't notice until I run it through lint, or the unit tests pick up on it. -- DavidCary

Scenario #2

Here is another way to write the above code to guarantee that b.Check() does not modify f.:

    const Foo f( 1234, "Some text" );

b.Check( f );

f.Something();

I can see at a glance that b.Check( f ) will not change f. I don't need to seek out Check()'s declaration or definition/s! Naturally, this change means f.Something() also cannot change f. I've started using const by default on most local variables (counters and such being obvious exceptions).

Since a call like the one to b.Check() could accept f by value, by reference, or by const reference, you cannot tell at a glance whether f can be modified by the call.

In a complex method that passes f to a dozen other methods, being able to determine instantly from the caller's code that none of the uses of f will modify it is absolutely golden. When I have to make changes to legacy code I make local variables const wherever I can. This immediately improves the readability of the code. I dread the thought of using a language without const.

Const by default for methods, data members, and local variables. Private by default for methods.

-- EricRunquist

Sounds like a wee bit of overkill here. const data members and variables are almost contradictory!

-- Anonymous

I agree that calling something a "constant variable" is a syntactic contradiction. It sounds better to call them "local constants" and "member constants". I disagree that this is overkill. This improves the readability of code at the expense of an additional qualifier on some definitions. It's just another way to clarify to both the compiler and future maintainers how you expect the code to behave.

Try this:

-- EricRunquist

Why would you make local variables const? Local numbers (int, etc.) should be either modifiable or enums; no const int. const char [] or const char * might apply to text strings, but it doesn't really add a great deal. The only case where I see this would be useful is for local objects and in this case it does clarify their intended use. It is much more helpful to apply const to the method, the calling parameters, and the return parameter as appropriate than to the internals of the method.

Why wouldn't it clarify the use of primitives? For example in

 const size_t n = myvector.size();
 for (size_t i = 0; i < n; ++i)
 doSomething(myvector[i], n);

it clarifies that doSomething isn't going to change n. Though the most common use is to clarify (and help guarantee) that the variable's value won't be changed anywhere in the same longish function body. -- ArneVogel

I don't think I like those names, since there is much room for ambiguity. How about "method constants" vs "class constants" (and "global constants" if you're using C or something that lets you have global variables)? -- GavinLambert

But then you'd have "method constants" and "constant methods"...


In the RefactoringBook, there is a refactoring, 'SeparateQueryFromModifier?'. This is only required (in C++!) for methods which were left non-const but perhaps have a part of them which performs a const task. Separation results in a const method (the Query) and a non-const method (the Modifier).

So using const full-time helps to remind you to keep the querying and modifying methods separate. I find that helpful. -- PaulMitchellGears


The place I find "const" most useful is in programming EmbeddedSystems, running a program on a microprocessor with only tiny amounts of RAM and ROM, using C++ as a glorified assembly language. Using "const" saves memory on data structures that are defined with some initial value, because then the compiler leaves that data in ROM. If I don't say "const", that data structure uses up *both* RAM (to hold the current value) *and* ROM (for the initial value, copied into RAM on power-on). Of course, this causes more "const" to cascade throughout the program. Every time I call a function/method with that "const" data, that parameter must be "const" in the function definition. -- DavidCary

You need a better compiler. The one I use (usually!) detects that the value is never written and leaves it in ROM.

Yes, I often find myself wishing for a better compiler. Which one do you use ? -- DavidCary

So far I haven't seen any compilers (not I've looked at that many compilers) that are smart enough to put

  int my_array = { 5, 4, 3, 2, 1, 0, };
into ROM unless I specifially say
  const int my_array = { 5, 4, 3, 2, 1, 0, };
-- DavidCary


Why would you make local variables const? Local numbers (int, etc.) should be either modifiable or enums; no const int.

Many former AssemblyLanguage programmers don't understand this. (In x86 assembly language programming, adding another local variable to a subroutine is a big hassle). They are used to moving stuff in and out of registers that can be used for many different things, and tend to name variables things like "i", "x", "y", "z", "tmp".

When I use MeaningfulNames, I often find that most of my local variables are "constant" during one function call.

Example:

2 styles of programming:

 // How much paint do I need to paint all 4 walls of a room?
 float paint_required( length, width, height, gallons_per_square_unit ){
    float x; // Is there a more MeaningfulName?
    x = length + width + length + width;
    x = x * height; // see? x is a *variable*, its value *varies*.
    x = x * gallons_per_square_unit;
    return x;
 }

I apply SplitTemporaryVariable and choose MeaningfulNames to get:

 // How much paint do I need to paint all 4 walls of a room?
 float paint_required( length, width, height, gallons_per_square_unit ){
    const float perimeter = length + width + length + width;
    const float area = perimeter * height;
    const float gallons = area * gallons_per_square_unit;
    return gallons;
 }

My understanding is that a decent compiler will generate exactly the same machine code for both programming styles.

Does that answer your question?

-- DavidCary

Actually, the version with consts might be easier for compilers to deal with. It can be trivially folded into one expression, making it easier for common subexpression elimination to find interesting parts to calculate only once, and possibly also helping other optimizations. The upcoming GCC release 4.0 is architected around static single assignment, which is essentially the same thing - each variable gets a value only once.


Hmmm. I have a little serial comms thing that has declarations like (from memory)

  const volatile BufferCount
which isn't ever modified by the place where it's defined, but rather by some other thread. I remember the day I first ran into const volatile and choked on my coffee. Ahh, threads. -- GarryHamilton


See ConstCorrectIsEncapsulation

CategoryCpp


EditText of this page (last edited April 8, 2008) or FindPage with title or text search