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;
int const j = 20;
i = 15;
j = 25;
}
const pointers
The const
keyword can be used with a pointer in two ways.
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)++;
j++;
}
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++;
(*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++;
(*j)++;
}
const and references
A reference is merely an alias for an entity. Here are some rules:
- A reference must be initialised
- Once initialized, a reference cannot be made to refer to another entity.
- Any change to the reference causes a change to the original entity and vice-versa.
- 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;
int j = 20;
int &r = i;
int &s;
i = 15;
i++;
r = 18;
r = j;
r++;
}
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;
s = 50;
i = 15;
j = 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;
MyClass()
{
i = 10;
}
~MyClass()
{
}
int ValueOfI() const
{
i++;
return i;
}
};
Overloading with const
The const
keyword can also be used with function overloading. Consider the following code:
class MyClass
{
public:
int i;
MyClass()
{
i = 10;
}
~MyClass()
{
}
int ValueOfI() const
{
return i;
}
int& ValueOfI()
{
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()
{
MyPoint.x = 0;
MyPoint.y = 0;
}
~MyClass()
{
}
MyPoint& MyPointIs()
{
return MyPoint;
}
};
int main(void)
{
MyClass m;
int x1 = m.MyPointIs().x;
int y1 = m.MyPointIs().y;
m.MyPointIs() = CPoint(20,20);
int x2 = m.MyPointIs().x;
int y2 = m.MyPointIs().y;
CPoint newPoint = m.MyPointIs();
}
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()
{
MyPoint.x = 0;
MyPoint.y = 0;
}
~MyClass()
{
}
SetPoint(CPoint point)
{
MyPoint.x = point.x;
MyPoint.y = point.y;
point.x = 100;
point.y = 101;
}
};
int main(void)
{
MyClass m;
CPoint newPoint(15,15);
m.SetPoint(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()
{
MyPoint.x = 0;
MyPoint.y = 0;
}
~MyClass()
{
}
SetPoint(const CPoint& point)
{
MyPoint.x = point.x;
MyPoint.y = point.y;
point.x = 100;
point.y = 101;
}
};
int main(void)
{
MyClass m;
CPoint newPoint(15,15);
m.SetPoint(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.