In lesson 5.1 --
Constant variables (named constants), you learned that objects of a fundamental data type
(int, double, char, etc…) can be made constant via the
const keyword. All const variables must be initialized at time of creation.
Similarly, class type objects (struct, classes, and unions) can also be made const by using the
const keyword. Such objects must also be initialized at the time of creation.
Just like with normal variables, you’ll generally want to make your class type objects const (or constexpr) when you need to ensure they aren’t modified after creation.
Modifying the data members of const objects is disallowed
Once a const class type object has been initialized, any attempt to modify the data members of the object is disallowed, as it would violate the const-ness of the object. This includes both changing member variables directly (if they are public), or calling member functions that set the value of member variables.
Const objects may not call non-const member functions
You may be surprised to find that this code also causes a compilation error:
Even though print() does not try to modify a member variable, our call to
today.print() is still a const violation. This happens because the print()
member function itself is not declared as const. The compiler won’t let us call a non-const member
function on a const object.
Const member functions
To address the above issue, we need to make print() a const member function. A const
member function is a member function that guarantees it will not modify the object or call
any non-const member functions (as they may modify the object).
Making print() a const member function is easy -- we simply append the const
keyword to the function prototype, after the parameter list, but before the function body:
In the above example, print() has been made a const member function, which means we can call
it on const objects (such as today).
For advanced readers
For member functions defined outside of the class definition, the const keyword must be
used on both the function declaration in the class definition, and on the function definition
outside the class definition. We show an example of this in lesson 15.2 -- Classes and
header files.
Constructors may not be made const, as they need to initialize the members of the object, which requires modifying them. We cover constructors in lesson 14.9 -- Introduction to constructors.
A const member function that attempts to change a data member or call a non-const member function will cause a compiler error to occur. For example:
In this example, incrementDay() has been marked as a const member function, but it attempts
to change day. This will cause a compiler error.
Const member functions can modify non-members (such as local variables and function parameters) and call non-member functions per usual. The const only applies to members.
Key insight
A const member function may not: modify the implicit object, call non-const member functions.
A
const member function may: modify objects that aren’t the implicit object, call const member
functions, call non-member functions.
Const member functions may be called on non-const objects
Const member functions may also be called on non-const objects:
Because const member functions can be called on both const and non-const objects, if a member function does not modify the state of the object, it should be made const.
Best practice
A member function that does not (and will not ever) modify the state of the object should be made const, so that it can be called on both const and non-const objects.
Be careful about what member functions you apply const to. Once a member function is made
const, that function can be called on const objects. Later removal of const on a member
function will break any code that calls that member function on a const object.
Const objects via pass by const reference
Although instantiating const local variables is one way to create const objects, a more common way to get a const object is by passing an object to a function by const reference.
In lesson 12.5 -- Pass by lvalue reference, we covered the merits of passing class type arguments by const reference instead of by value. To recap, passing a class type argument by value causes a copy of the class to be made (which is slow) -- most of the time, we don’t need a copy, a reference to the original argument works just fine and avoids making a copy. We typically make the reference const to allow the function to accept const lvalue arguments and rvalue arguments (e.g. literals and temporary objects).
Can you figure out what’s wrong with the following code?
The answer is that inside of the doSomething() function, date is treated as a
const object (because it was passed by const reference). And with that const date, we’re
calling non-const member function print(). Since we can’t call non-const member functions
on const objects, this will cause a compile error.
The fix is simple: make print() const:
Now in function doSomething(), const date will be able to successfully call
const member function print().
Member function const and non-const overloading
Finally, although it is not done very often, it is possible to overload a member function to have a const and non-const version of the same function. This works because the const qualifier is considered part of the function’s signature, so two functions which differ only in their const-ness are considered distinct.
This prints:
Overloading a function with a const and non-const version is typically done when the return value needs to differ in constness. This is pretty rare.