Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The Story of Const

0.00/5 (No votes)
21 May 2003 1  
This is an article about the const keyword, its details and why you should worry about it.

Introduction

For a long time I would have to stop and think before I put the const keyword in my code or when I read it someone else's code. A lot of programmers just don't worry about the const keyword and their justification is that they have gotten along just fine so far without it. With the aim of emphasizing improved software and code quality, this article is supposed to be a one place stop for the story of const. Beginners will hopefully find it an easy introduction to the different ways of using const and experienced programers can use the examples here as a quick reference.

Small Disclaimer/Caution

All the code presented below in the examples has been tested (if at all) very casually and so please analyse it carefully before using it. The pointer arithmetic esp. can be a cause for serious grief and care should be taken when writing production code.

const variables

When a variable is prefixed with the key word const its value cannot be changed. Any change will result in a compiler error. It makes no difference if the const keyword appears before the data type or after it.

Here is an example:

int main(void)
{
    const int i = 10;  //both variables i and j are constants

    int const j = 20;
    i = 15;            //Error, cannot modify const object

    j = 25;            //Error, cannot modify const object

}

const pointers

The const keyword can be used with a pointer in two ways.

  1. const can be used to specify that the pointer is constant i.e. the memory address that the pointer points to cannot be changed. The current value at that memory address however can be changed at whim.
    int main(void) 
    { 
        int i = 10;
        int *const j = &i;  //j is a constant pointer to int
    
        (*j)++;     //The int value pointed to by j can be modified.
    
                    // i.e. *j = i = 11;
    
        j++;        //ERROR: cannot modify const object j
    
    }
    
  2. const can also be used with a pointer to specify that the value at the memory address cannot be changed but the pointer can be assigned to a new address.
    int main(void)
    {
        int i = 20;
        const int *j = &i;  //j is a pointer to a constant int
    
        *j++;     //the pointer value of j can be incremented. 
    
                  //Note that the value at pointer location j 
    
                  //may be garbage or cause a GPF 
    
                  //but as far as the const keyword is concerned 
    
                  //the compiler doesnt care
    
        (*j)++;   //ERROR; cannot modify const object *j
    
    }
    

A good way to look at the two examples above is that in the first case j is a constant (constant pointer to int) while in the second case the *j is constant (pointer to a constant int).

The two can be combined so that neither the pointer nor its value can be changed.

int main(void)
{
    int i = 10;
    const int *const j = &i; //j is a constant pointer to constant int

    j++;                //ERROR: cannot modify const object j

    (*j)++;          //ERROR: cannot modify const object *j    

}

const and references

A reference is merely an alias for an entity. Here are some rules:

  1. A reference must be initialised
  2. Once initialized, a reference cannot be made to refer to another entity.
  3. Any change to the reference causes a change to the original entity and vice-versa.
  4. A reference and an entity both point to the same memory location.

Here is an example to illustrate the rules above:

int main(void)
{ 
    int i = 10;     //i and j are int variables 

    int j = 20;    
    int &r = i;    //r is a reference to i 

    int &s;        //error, reference must be initialized 

    i = 15;         //i and r are now both equal to 15. 

    i++;         //i and r are now both equal to 16 

    r = 18;        //i and r are now both equal to 18 

    r = j;        //i and r are now both equal to 20, 

               //the value of j. However r does not refer to j.

    r++;        //i and r are now both equal to 21, 

                //but j is still 20 since r does not refer to j

}

Using const with a reference ensures that the reference cannot be modified. Any change however to the entity that the reference refers to will be reflected by a change in the reference. It also makes no difference whether the const keyword appears before or after the data type specifier.

Here is an example:

int main(void)
{
    int i = 10;
    int j = 100;
    const int &r = i;
    int const &s = j;
    r = 20;          //error, cannot modify const object

    s = 50;          //error, cannot modify const object

    i = 15;          //both i and r are now 15

    j = 25;          //both j and s are now 25

}

const with member functions

When the const keyword is appended to a member function declaration, then the object pointed to by the this pointer cannot be modified. This is frequently used when defining accessor methods that are only used to read the value of the object data. This is useful because developers using your code can look a the declaration and be certain that your accessor method won't modify the object in any way.

Here is an example:

class MyClass 
{ 
public:
    int i;    //member variable

    MyClass()
    {
        //void constructor    

        i = 10;
    }
    
    ~MyClass()
    {
        //destructor

    }

    int ValueOfI() const    //const method is an accessor method

    {
        i++;        //error, cannot modify object

        return i;    //return the value of i

    }
};

Overloading with const

The const keyword can also be used with function overloading. Consider the following code:

class MyClass
{
public:
    int i;    //member variable

    MyClass()
    {
        //void constructor    

        i = 10;
    }
    
    ~MyClass()
    {
        //destructor

    }

    int ValueOfI() const //const method is an accessor method

    {
        return i;        //return the value of i.    

    }
    
    int& ValueOfI()      //method is used to set the 

                         //value by returning a reference

    {
        return i;
    }
};

In the above example the two methods ValueOfI() are actually overloaded. The const keyword is actually part of the parameter list and specifies that the this pointer in the first declaration is constant while the second is not. This is a contrived example to emphasize the point that const can be used for function overloading.

In reality due to the beauty of references just the second definition of ValueOfI() is actually required. If the ValueOfI() method is used on the the right side of an = operator then it will act as an accessor, while if it is used as an l-value (left hand side value) then the returned reference will be modified and the method will be used as setter. This is frequently done when overloading operators.

class MyClass 
{ 
public:
    CPoint MyPoint; 
    MyClass() 
    {
        //void    constructor 

        MyPoint.x = 0; 
        MyPoint.y = 0;
    }
    ~MyClass()
    { 
        //destructor

    }
    
    MyPoint& MyPointIs()    //method is used to set the value 

                            //by returning a reference 

    { 
        return MyPoint;

    }
};

int main(void)
{
    MyClass m;
    int x1 = m.MyPointIs().x;        //x1 = 0

    int y1 = m.MyPointIs().y;        //y1 = 0

    m.MyPointIs() = CPoint(20,20);   //m.MyPoint.x = 20, m.MyPoint.y = 20

    int x2 = m.MyPointIs().x;        //x2 = 20

    int y2 = m.MyPointIs().y;        //y2 = 20

    CPoint newPoint = m.MyPointIs();    //newPoint = (20,20)

}
As seen in the main function above the same MyPointIs() method is being used as an accessor where it is used to set the values of x1 & y1 and also as a setter when it is used as an l-value. This works because the myPointIs() method returns a reference that gets modified by the value on the right hand side (CPoint(20,20)).

Why should I worry about this const gubbins?

Data is passed into functions by value by default in C/C++. This means that when an argument is passed to a function a copy is made. Thus any change to the parameter within the function will not affect the parameter outside the function. The downside is that everytime the function is called a copy must be made and this can be inefficient. This is especially true if the function is called within a loop that executes (say) a 1000 times.

Here is an example:

class MyClass
{
public:
    CPoint MyPoint;
    MyClass()
    {
        //void constructor

        MyPoint.x = 0;
        MyPoint.y = 0;
    }
    ~MyClass()
    {
        //destructor

    }

    SetPoint(CPoint point)    //passing by value 

    {
        MyPoint.x = point.x;
        MyPoint.y = point.y;

        point.x = 100;        //Modifying the parameter has 

                              //no effect outside the method

        point.y = 101;    
    }
};

int main(void)
{
    MyClass m;
    CPoint newPoint(15,15);
    m.SetPoint(newPoint);   //m.Mypoint is (15,15) and so is newPoint.

}

As seen in the above example the value of newPoint remains unchanged because the SetPoint() method operates on a copy of newPoint. To improve efficieny we can pass parameters by reference rather than by value. In this case a reference to the parameter is passed to the function and no copy is needed to be made. However now the problem is that if the parameter is modified in the method as above then the variable outside the method will also change leading to possible bugs. Prefixing const to the parameter ensures that the parameter cannot be modified from within the method.

class MyClass
{
public:
    CPoint MyPoint;
    MyClass()
    {
        //void constructor

        MyPoint.x = 0;
        MyPoint.y = 0;
    }
    ~MyClass()
    {
        //destructor

    }

    SetPoint(const CPoint& point)   //passing by reference

    {
        MyPoint.x = point.x;
        MyPoint.y = point.y;

        point.x = 100;        //error, cannot modify const object

        point.y = 101;        //error, cannot modify const object


    }
};

int main(void)
{
    MyClass m;
    CPoint newPoint(15,15);
    m.SetPoint(newPoint);        //m.Mypoint is (15,15) and so is newPoint.

}

In this case const is used a safety mechanism that prohibits you from writing code that could come back and bite you. You should try to use const references far as possible. By declaring you method arguments as const (where appropriate), or declare const methods you are ineffect making a contract that the method will never change the value of an argument or never modify the object data. Thus other programmers can be sure that the method you provide won't clobber the data that they pass to it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here