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:
- Will
containsMonkey( string& )change the value of my name parameter? - Will
addMonkey( Monkey* )change the value of my monkey? - Will
removeMonkey( Monkey* )change the value of my monkey?
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.
add to del.icio.us




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.