C++ const Keyword

In C++, Programming by timfanelliLeave a Comment

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.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.