Const Correctness In Cpp

From DmWiki

Table of contents

1 Additional Resources

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



DevMaster navigation