Const Correctness In Cpp
From DmWiki
| Table of contents |
What Is Const
The const keyword is used to indicate that the data being referred to should not change beyond its initial initialisation. It is used as a reference for the programmer indicating what they may do with the data, and how it should be used. When working on new areas of code (or even when maintaining legacy code), your policy should be to initially use const everywhere, only removing the keyword when it becomes apparent that the data being used must be altered for the program to work correctly.
One thing to note is that const does not always allow the compiler to optimize code beyond what it already written (there are exceptions, but this is a general rule). Since it is possible to remove const (see Const Casting below), the compiler cannot assume that the variable will never change, so const should always be used a programming tool to aid readability of code above all else.
Const Variables and Parameters
The easiest use of const is on variables like the following
int size = 1; // Normal variable that is not const size = 2; // Changing the size is legal const int size = 1; // Normal variable set as const size = 2; // error - we cannot change const variables
Due to this ability to lock the variable's value, const should always be used in replace of #define's. This allows the value to take advantage of type checking, and allows the scope to be reduced if needed (which in most cases it is!).
#define SIZE 2 // Nasty #define to remove a magic number const uint SIZE = 2 // const variable with all the benefits of type checking etc.
Const can also be used to indicate how a variable will be used when it is passed as a parameter to a function.
bool IsSizeValid(uint size);
What this function does is obvious isn't it? What does it do with the size parameter and what does it expect you to do with the return value? If we follow the principle of const-ing everything until we really can't do without it, we would have this,
const bool IsSizeValid(const uint size);
Now what does this tell you about the function? It says if you pass the size into the function, it will use that parameter exactly as you send it. And the return value – the function is requesting that you don't change it, and why should you? While it is easy to say this is over-kill, by using const you have shown the reader exactly what this function call expects, and exactly what they should expect from it.
This becomes more apparent when using references:
const bool IsWithinRange(const CPlayer& player);
Without the const qualifier, the function would have access to the internals of the CPlayer object, and the caller could not guarantee that the object was the same as when the function was first called. Applying const tells the caller that nothing will happen to this object, and its state after the call will be identical to the state before the call.
Const Data Members
Using const data members allows us to place information within the scope of a class while still having all the benefits of type checking and being able to avoid #defined values.
class CPlayer
{
static const uint maxNumberOfWeapons = 10;
};
Due to the way the compiler handles the const value, we do not have to define the value, just declaring it in the header file is sufficient. This can only be achived by using integral values, and declaring them as static consts. Depending on the platform, the compiler will store these values differently, so only use them to reference the value and not the address (some compilers store static const members with the same value in the same register) and never change them using const_cast (see below).
To add even more to const data members, it is possible to declare them as public, protected or private within the class, giving them even more scope and furthering the use of them as indications of use and not just ways of avoiding magic numbers.
Const Pointers
Const pointers can be confusing due to the different ways in which const can be used. Take the following variable
CPlayer* player;
Now we want this to be a const variable, but what do we want to be const? The data that is being pointed to, or the pointer itself. First, lets make the data being pointed to const so we cannot change the data contained within the player object
const CPlayer* player = world->GetPlayer(); // Adding const at the beginning makes the player object const player->health = 100; // Illegal, cannot change the value of a variable declared as const player = world->GetAnotherPlayer(); // Legal as we can change the pointer
If we were happy to allow the code to change the contents of the player object, but not the actual pointer, we need to declare that as const.
CPlayer* const player = world->GetPlayer(); // Adding const at the end makes the pointer const only player->health = 100; // This is now legal, as only the pointer is const player = world->GetAnotherPlayer() // Illegal, we cannot change the pointer
And yes, we can use both of them to make both the pointer and the object const
const CPlayer* const player = world->GetPlayer(); // Adding const at the end makes the pointer const only player->health = 100; // Illegal, cannot change the value of a variable declared as const player = world->GetAnotherPlayer() // Illegal, we cannot change the pointer
Const Methods
Const methods are used to indicate that the method will not altered the visible structure of the object and it will not call any other methods that themselves are not const. If a particular member function does not have a const qualifier, a pointer to a const object cannot call that method as the compiler cannot guarantee that the const-ness of the object will be respected.
Class CPlayer
{
const char* GetName(void);
};
const CPlayer* player = world->GetPlayer();
// Illegal, as the compiler can't trust the method not to change the internal properties of the object...
player->GetName();
To get around this, we can declare GetName as const, so the compiler sees it as a safe call.
Class CPlayer
{
const char* GetName(void) const; // The function now guarantees the objects state will not change
};
const CPlayer* player = world->GetPlayer();
// This is now legal, as the compiler knows the internals of the object will not change
player->GetName();
Mutable Members
What happens if a const function needs to change the internals of an object. First you need to think if this method should be const at all, since it is going to change the structure of the objects. But, if the internal data is only ever used by the object itself, and by changing the data it won't effect the external face of the object, you can use the mutable keyword.
Take the GetName function above. It may need to get the name from an external source, but cache it for later use. If we didn't use the mutable keyword, the following would be illegal (bear in mind this example is used as a demonstration, and it not real production code!).
Class CPlayer
{
public:
const char* GetName(void) const
{
if(m_nameCached == TRUE)
{
return m_cachedName;
}
else
{
// Cache the name
strncpy(m_cachedName, GetPlayerNameFromExternalSource(), 16);
m_nameCached = TRUE;
}
}
private:
bool m_nameCached;
char m_cachedName[17];
};
If we wanted the const function to be allowed to modify the internal members, we would declare them as mutable
Class CPlayer
{
public:
const char* GetName(void) const;
private:
mutable bool m_nameCached;
mutable char m_cachedName[17];
};
Const Methods Don't Actually Exist
What does a const method actually do, and why don't they actually exist? By declaring a method as const, we are actually declaring the this pointer as const, rather than the function itself. Knowing this can sometimes help you figure out why the compiler is complaining about a function call that looks perfectly legal!
Const Casting
As mentioned above, the compiler will not generally use the const keyword as an optimization technique, because it cannot guarantee that the variable will not change – and this is due to the ability to cast away const using const_cast.
In most situations, there is no need to use const_cast, as a variable is declared const for a reason, and a programmer has no right in removing this. Unfortunately, situations exist where a function that should take a const variable doesn't, and this means either removing the const-ness of your program to fit in, or removing the const-ness from the local function call.
void UpdateObject(const CObject& myObject)
{
localObject->UpdateObject (myObject); // Illegal, as this local object doesn't take a const object and it should!
}
Until this can be fixed (and it usually can be fixed by discussing the problem with the library provider), you will need to remove the const-ness of your object.
void UpdateObject(const CObject& myObject)
{
localObject-> UpdateObject ( const_cast<CObject&>(myObject) ); // This is now legal, but very very bad!
}
It cannot be stressed enough that casting const away is a bad thing, and is a symptom of either bad design or planning. If you find yourself needing to do this, more work should be done in regards to program structure and re-designing.
Advantages Of Using Const
- Correctness: It is impossible to change a value that should remain the same, and you can catch this at compile time rather than waiting for a run-time error.
- Reliability: Users of your objects are guaranteed that your methods will not alter the values passed or the state of the object, making the program more secure and less prone to error.
- Documentation: You don't need to investigate the code to figure out what is being modified and what isn't. This also helps with future maintenance.
- Efficiency: Some compilers will optimise Const Data Members to use less memory that normal data members
Disadvantages Of Using Const
- Speed: You will have to type more and adding const can have a chain reaction that will propagate through your code (not a bad thing, but can be difficult to fix)
Additional Resources
- http://www.possibility.com/Cpp/const.html – Walkthrough on using const in code
- http://en.wikipedia.org/wiki/Const – General information on const correctness
- http://www.gotw.ca/gotw/081.htm – Const correctness and optimisations
