In lesson 4.14 -- Const, constexpr, and symbolic constants, you learned that fundamental data types (int, double, char, etc…) can be made const via the const keyword, and that all const variables must be initialized at time of creation.
In the case of const fundamental data types, initialization can be done through copy, direct, or uniform initialization:
1 2 3 |
const int value1 = 5; // copy initialization const int value2(7); // direct initialization const int value3 { 9 }; // uniform initialization (C++11) |
Const classes
Similarly, instantiated class objects can also be made const by using the const keyword. Initialization is done via class constructors:
1 2 3 |
const Date date1; // initialize using default constructor const Date date2(2020, 10, 16); // initialize using parameterized constructor const Date date3 { 2020, 10, 16 }; // initialize using parameterized constructor (C++11) |
Once a const class object has been initialized via constructor, any attempt to modify the member variables 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. Consider the following class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Something { public: int m_value; Something(): m_value{0} { } void setValue(int value) { m_value = value; } int getValue() { return m_value ; } }; int main() { const Something something{}; // calls default constructor something.m_value = 5; // compiler error: violates const something.setValue(5); // compiler error: violates const return 0; } |
Both of the above lines involving variable something are illegal because they violate the constness of something by either attempting to change a member variable directly, or by calling a member function that attempts to change a member variable.
Just like with normal variables, you’ll generally want to make your class objects const when you need to ensure they aren’t modified after creation.
Const member functions
Now, consider the following line of code:
1 |
std::cout << something.getValue(); |
Perhaps surprisingly, this will also cause a compile error, even though getValue() doesn’t do anything to change a member variable! It turns out that const class objects can only explicitly call const member functions, and getValue() has not been marked as 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).
To make getValue() a const member function, we simply append the const keyword to the function prototype, after the parameter list, but before the function body:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Something { public: int m_value; Something(): m_value{0} { } void resetValue() { m_value = 0; } void setValue(int value) { m_value = value; } int getValue() const { return m_value; } // note addition of const keyword after parameter list, but before function body }; |
Now getValue() has been made a const member function, which means we can call it on any const objects.
For member functions defined outside of the class definition, the const keyword must be used on both the function prototype in the class definition and on the function definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Something { public: int m_value; Something(): m_value{0} { } void resetValue() { m_value = 0; } void setValue(int value) { m_value = value; } int getValue() const; // note addition of const keyword here }; int Something::getValue() const // and here { return m_value; } |
Futhermore, any const member function that attempts to change a member variable or call a non-const member function will cause a compiler error to occur. For example:
1 2 3 4 5 6 7 |
class Something { public: int m_value ; void resetValue() const { m_value = 0; } // compile error, const functions can't change member variables. }; |
In this example, resetValue() has been marked as a const member function, but it attempts to change m_value. This will cause a compiler error.
Note that constructors cannot be marked as const. This is because constructors need to be able to initialize their member variables, and a const constructor would not be able to do so. Consequently, the language disallows const constructors.
Rule
Make any member function that does not modify the state of the class object const, so that it can be called by const objects.
Const references
Although instantiating const class objects is one way to create const objects, a more common way is by passing an object to a function by const reference.
In the lesson on passing arguments by reference, we covered the merits of passing class arguments by const reference instead of by value. To recap, passing a class 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 is more performant because it avoids the needless copy. We typically make the reference const in order to ensure the function does not inadvertently change the argument, and to allow the function to work with R-values (e.g. literals), which can be passed as const references, but not non-const references.
Can you figure out what’s wrong with the following code?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#include <iostream> class Date { private: int m_year; int m_month; int m_day; public: Date(int year, int month, int day) { setDate(year, month, day); } void setDate(int year, int month, int day) { m_year = year; m_month = month; m_day = day; } int getYear() { return m_year; } int getMonth() { return m_month; } int getDay() { return m_day; } }; // note: We're passing date by const reference here to avoid making a copy of date void printDate(const Date &date) { std::cout << date.getYear() << '/' << date.getMonth() << '/' << date.getDay() << '\n'; } int main() { Date date{2016, 10, 16}; printDate(date); return 0; } |
The answer is that inside of the printDate function, date is treated as a const object. And with that const date, we’re calling functions getYear(), getMonth(), and getDay(), which are all non-const. Since we can’t call non-const member functions on const objects, this will cause a compile error.
The fix is simple: make getYear(), getMonth(), and getDay() const:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Date { private: int m_year; int m_month; int m_day; public: Date(int year, int month, int day) { setDate(year, month, day); } // setDate() cannot be const, modifies member variables void setDate(int year, int month, int day) { m_year = year; m_month = month; m_day = day; } // The following getters can all be made const int getYear() const { return m_year; } int getMonth() const { return m_month; } int getDay() const { return m_day; } }; |
Now in function printDate(), const date will be able to successfully call getYear(), getMonth(), and getDay().
Overloading const and non-const function
Finally, although it is not done very often, it is possible to overload a function in such a way to have a const and non-const version of the same function:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <string> class Something { private: std::string m_value; public: Something(const std::string &value=""): m_value{ value } {} const std::string& getValue() const { return m_value; } // getValue() for const objects std::string& getValue() { return m_value; } // getValue() for non-const objects }; |
The const version of the function will be called on any const objects, and the non-const version will be called on any non-const objects:
1 2 3 4 5 6 7 8 9 10 |
int main() { Something something{}; something.getValue() = "Hi"; // calls non-const getValue(); const Something something2{}; something2.getValue(); // calls const getValue(); return 0; } |
Overloading a function with a const and non-const version is typically done when the return value needs to differ in constness. In the example above, the non-const version of getValue() will only work with non-const objects, but is more flexible in that we can use it to both read and write m_value (which we do by assigning the string “Hi”).
The const version of getValue() will work with either const or non-const objects, but returns a const reference, to ensure we can’t modify the const object’s data.
This works because the const-ness of the function is considered part of the function’s signature, so a const and non-const function which differ only in const-ness are considered distinct.
Summary
Because passing objects by const reference is common, your classes should be const-friendly. That means making any member function that does not modify the state of the class object const!
![]() |
![]() |
![]() |
When creating an object, what is the difference between the following two statements if both call the default constructor?
and is the first form legal when initializing a const object?
Whether or not `something` is initialized depends on `Something`.
`something` is initialized.
List-initialization is easier to understand and safer.
Thank's a lot
I just found that List-initialization will initialize the member variable to 0 if it's not initialized by the class itself
So in a previous lecture, you discussed the way in which C++ will implicitly convert member functions. So a member function of a class (say, class Foo) like this:
gets converted to a function like this under the hood:
I was just curious if a similar conversion process also occurs when we mark a member function as const. For instance, is the following member function (from the same Foo class)...
... implicitly converted to a function like this:
In other words, making the const this pointer a const pointer to a const this? And if this is the case, I think it might be useful to include it for academic purposes. Thanks for any clarifications!
1. Feedback on Style
Very confusing to see your constructor in another style
You've been using the following for a while
I suddenly got a doubt. What does it mean when we put the 'const' keyword before or after a function's return type? I'm not asking in this particular context, I'm asking generally
Returns a reference to a `std::string`. `getValue()` is not callable on `const` objects.
Returns a `const` reference to a `std::string`. `getValue()` is not callable on `const` objects.
Returns a non-const reference and is callable on const objects
Returns a const reference and is callable on const objects
Okay now it's all clear. Thank you
The reason the return value is const reference not just reference in the code above is to prevent the caller from doing the following?
I was wondering why I didn't get any compile time error on lines #24 and #27 because the member function doesn't return reference type? Aren't we supposed to assign to a function when the function return a reference type?
> The reason the return value is const reference not just reference in the code above is to prevent the caller from doing the following?
That's right.
> why I didn't get any compile time error
The assignment operator for custom types doesn't have any special rules when it gets to r-values
There's nothing stopping you from doing (c), and because the assignment operator follows the same rules, (b) and (a) are also legal.
I can't think of an example where it would make sense to use the assignment operator like this though.
What does this compiler error mean when we are attempting to set a member variable of the object 'Something' defined const?
AND I think 'this' is an implicit parameter for normal member functions. How come the compiler mention 'this' in the 'main' function?
'void Something::setValue(int)': cannot convert 'this' pointer from 'const Something' to 'Something &'
If you don't mark a function as `const`, the compiler assumes that the function modifies the object. You can't call a function that modifies the object if the object is `const`.
`this` refers to whatever you called the function on. In your case, it's referring to `something`.
Excellent tutorials , I believe this information has been presented very clearly !
Just thought I'd let you know, none of the code areas actually look like code areas and it made the lesson kind of hard to follow.
I have a question regarding const member function.Say a class has 2 similar member functions differing only by const signature.If I try to call the member function using non-const object I believe the non const member function gets called, how can I call const member function ? If function only has const member function, will it be called if I call it using non-const object ?
> If I try to call the member function using non-const object I believe the non const member function gets called
Correct
> how can I call const member function ?
I can't think of a reason to do this. Anyway, you can use `const_cast`
Hi,
The answer is that inside of the printDate function, date is treated as a const object. And with that const date, we’re calling functions getYear(), getMonth(), and getDay(), which are all non-const. Since we can’t call non-const member functions on const objects, this will cause a compile error.
You explained this . Is passing an object by const reference as a constructor parameter follows the same rule ?
In this
If we have passed object as const reference in constructor doesn't std::string& getValue() { return m_value; }
this should be const function only then we can use it ?
Thanks,
Ruchika
can I expect a reply ?
You can't call non-const function on a const object. No matter where that object came from.
Then how come we are using this -
std::string& getValue() { return m_value; } // getValue() for non-const objects
Here object is coming from constructor as const reference .
I didn't get your point for constructors.
this function is called by `something.getValue`. `something` is non-const.
I think you might be confused by
in the constructor. This is completely irrelevant to the member functions.
yaa .
Thanks for clearing !
Hi,
"Once a const class object has been initialized via constructor, any attempt to modify the member variables of the object is disallowed, as it would violate the const-ness of the object."
Why is the member variable (m_cents) of the object (cent) allowed to get changed as shown below?
`m_cents` doesn't change after the construction. `getCents` creates a new `int` with value `-m_cents`, but `m_cents` stays the same.
If you had typed "--m_cents" the compiler would have probably complained.