The C++ const Keyword

Posted Mon, 17 Oct 2005

const is, in my opinion, the most influential and least frequeuently used keyword in the C++ programming language.

This may seem like a very bold statement, but having recently started work on a relatively large C++ application, I'm finding that developer's lack of const usage is hindering my ability to write solid code. I'm trying desperately not to fall into the trap that many developers do, of abondonding const because it'd been forgotten; but the Software Engineer in me can't let go, and I'm spending a lot of time re-writing code with proper const usage.

Lets look at a simple example of how many developers would write a simple class:

#include <string>
using namespace std;

class Monkey {
public:
  Monkey( string name );

  string GetName();
  
private:
  string name;
};

Simple enough. It's a monkey with a name. Let's say I manage the Monkey House at the zoo. I might then want to have a collection of monkeys available to me:

#include <map>
#include <string>
#include "monkey.h"

using namespace std;

class MonkeyHouse {
public:
  bool containsMonkey( string & name );
  void addMonkey( Monkey* m );
  void removeMonkey( Monkey* m );
  Monkey * getMonkey( string &name );

private:
  map< string, Monkey* > myMonkeys;
};

Again, simple enough. Too bad it's horrendous. This simple API raises numerous questions that should be obvious to the conscientous developer:

  1. Will containsMonkey( string& ) change the value of my name parameter?
  2. Will addMonkey( Monkey* ) change the value of my monkey?
  3. Will removeMonkey( Monkey* ) change the value of my monkey?
And more importantly for each of these questions, if the answer is yes, then when -- before or after it performs is expected function?

Lets say you were dropped into this project and wanted to make it "more solid". We can start by addressing those questions above with the const keyword:

class MonkeyHouse {
public:
  bool containsMonkey( const string & name );
  void addMonkey( const Monkey* m );
  void removeMonkey( const Monkey* m );
  Monkey * getMonkey( const string &name );

private:
  map< string, Monkey* > myMonkeys;
};

This simple addition of const to your method parameters ensures users of your API that objects passed will not be changed by that method. Remember that a const Monkey * m though is a pointer to a const monkey. The method could still change which monkey is pointed to my m if it so chooses. A slightly safer declaration might look like this:

class MonkeyHouse {
public:
  bool containsMonkey( const string & name );
  void addMonkey( const Monkey* const m );
  void removeMonkey( const Monkey* const m );
  Monkey * getMonkey( const string &name );

private:
  map< string, Monkey* > myMonkeys;
};

Now the methods that take monkey pointers take constant pointers to constant monkeys. Users of your API can now be utterly certain that the method will not change the monkey, nor will it change the monkey you're pointing too. Good! This is a great start.

Too bad it won't compile.

I think it's safe to assume that addMonkey( const Monkey* const m ) looks a lot like this:

void Monkey::addMonkey( const Monkey* const m ) {
  myMonkeys[ m->GetName() ] = m;
}

We can't assign m to myMonkeys[ key ] though, because myMonkeys contains pointers to monkeys, not pointers to constant monkeys. You'll get a cast error. Fixing the map declaration is easy enough though, we'll just make it:

  map< string, const Monkey* > myMonkeys;

Great! Too bad it won't compile.

You're now assigning a pointer to a constant monkey to myMonkeys[ m->GetName() ]. However, you can't call Monkey::GetName() on a constant monkey, because it's not a constant method! Argh! Lets revisit the Monkey API, shall we?

class Monkey {
public:
  Monkey( string name );

  string GetName() const;
  
private:
  string name;
};

By declaring GetName() to be const we are saying that GetName will not change the value of this Monkey instance. I can now call GetName() on constant monkeys.

Great! Too bad in won't compile...

MonkeyHouse's getMonkey( const string &name ); method returns a Monkey*. The implementation will look a lot like this:

Monkey * MonkeyHouse::getMonkey( const string &name ) {
   return myMonkeys[ name ];
}

But remember, myMonkeys[name] is a const Monkey*! We need to change the method signature to:

const Monkey * GetMonkey( const string &name );

Now call me crazy, but just for this toy example, that's an AWFUL lot of work to add some safety. Imagine having to do this in a large, 5th generation, production level desktop application! So my plea to all you developers out there, C++ or not, STOP BEING LAZY. Pay attention to your own work. Excersize your right to say "NO! You can't change this value, and NO! I won't change yours." It's only five little letters and a whitespace token. It's not difficult, and I won't feel the need to hunt you down later.

Related Books

Exceptional C++ Style: 40 New Engineering Puzzles, Programming Problems, and Solutions (C++ In-Depth Series) C Programming Language (2nd Edition) (Prentice Hall Software) C++ Common Knowledge: Essential Intermediate Programming Cocoa(R) Programming for Mac(R) OS X (3rd Edition) More Effective C++: 35 New Ways to Improve Your Programs and Designs (Addison-Wesley Professional Computing Series)

Comments

On June 22, 2008 at 07:06 PM James wrote:

Hmmm. "const Monkey * const monkey"

You'd think "const Monkey *" would mean a constant (Monkey) pointer, as it states, but it actually means the thing you're pointing to is constant.

You'd think "Monkey * const" would mean a (Monkey) pointer to a constant value, as it states, but it actually means the opposite.

The syntax is backwards, obnoxious, unnecessarily restrictive, and causes compilation errors when you decide you want something to change after all.

Just because a function doesn't change anything, doesn't mean I won't want it to later; yet the compiler would force me to put const in the function header prematurely, which would subsequently affect other code.

This use of const is obsolete, IMHO; it's not even used in C#. If you pass a reference type (whether it's a pointer or an object reference), it might get changed; that's usually why we pass references. This whole idea of being able to pass a "read-only reference to read-only data" is useless. Programmers just need to pay attention to what they're doing with data, the size of the data they're working with, and make sure you free data that you allocate. There's no magic keyword that can do ALL of this for you, so forget about const, pay attention, and keep track of your data.

On June 22, 2008 at 07:06 PM Pamela wrote:

http://anticworld.com/

On June 22, 2008 at 07:06 PM James wrote:

Hmmm. "const Monkey * const monkey"

You'd think "const Monkey *" would mean a constant (Monkey) pointer, as it states, but it actually means the thing you're pointing to is constant.

You'd think "Monkey * const" would mean a (Monkey) pointer to a constant value, as it states, but it actually means the opposite.

The syntax is backwards, obnoxious, unnecessarily restrictive, and causes compilation errors when you decide you want something to change after all.

Just because a function doesn't change anything, doesn't mean I won't want it to later; yet the compiler would force me to put const in the function header prematurely, which would subsequently affect other code.

This use of const is obsolete; it's not even used in C#. If you pass a reference type (whether it's a pointer or an object reference), it might get changed; that's usually why we pass references. This whole idea of being able to pass a "read-only reference to read-only data" is useless. Programmers just need to pay attention to what they're doing with data, the size of the data they're working with, and make sure you free data that you allocate. There's no magic keyword that can do ALL of this for you, so forget about const, and just pay attention to your data.

On June 22, 2008 at 07:06 PM James wrote:

Sorry about the double post; issues with email confirmation, but anyway...

I should also mention that calling "black-box" functions is the biggest mistake in programming. If you really need "const" to tell you whether a function is going to change your data, then you have much bigger issues. If you're calling a function and you're so clueless about what it does that you don't even know whether it's going to change your data (hence you need const keyword), then you shouldn't even be calling the function, IMHO.

Everything you write IS ALWAYS part of a larger system. The more you know about the complete system, the more robust your code will be. The more people follow a black-box model, the more surprises you'll get. It's just an inconvenient truth.

On August 08, 2008 at 07:13 PM Tim Fanelli wrote:

James -

I whole-heartedly, but respectfully, disagree. Relying on programmers to pay attention to what they're doing is one of the sole-causes of most security and data-integrity vulnerabilities in modern software. When developing software libraries, it is of utmost importance to tell developers "this is what you can do," and have it enforced by the runtime system. The C++ const keyword is one powerful mechanism for doing so.

Additionally, when using the C++ standard template libraries, you often have little choice but to use the const keyword appropriately. The iterator patterns used in the STL often only allow you access to a container's elements via a const_iterator, which when dereferenced results in a const pointer. If you have not properly applied the const keyword in the implementation of your class definitions, you will have little, if any, ability to use the reference given you by the container.

While I agree the syntax is confusing, when read allowed I believe it makes perfect sense. "const Monkey *" is a pointer to a constant monkey. "Monkey * const" is a constant pointer to a non-constant monkey. One is applicable to the data type, the other to the value on the stack (in other words, the pointer value).

And just because languages such as C# do not have all the same syntactic constructs as C/C++ does not make use of the keyword obsolete. C# has no need for constant pointers ("Monkey * const") because of the drasitically different way it deals with references than C++. You're comparing apples to potatos.

Post a Comment




About

My name is Tim Fanelli, I am a software engineer in Northern NY. I spend most of my time working, and when I can, I try to post interesting things here.

Cigar Dossiers