Skip to main content

Section 17.1 Const Member Functions

Subsection 17.1.1 Objects as Parameters

We can use objects as parameters to functions in the same way we use structs. Just as with structs, we need to consider whether we want to pass the object by value or by reference. Passing by reference is usually the better choice, since it avoids copying the object. This is especially important for large objects, where copying can be expensive in terms of time and memory. And, if we don’t want to modify the object, we should pass it as a const reference. This way, we can be sure that the function won’t change the object.
However, there is an extra wrinkle. If we include the header of a Point class and then try to compile this code:
double distanceBetween(const Point& p1, const Point& p2) {
    double xdiff = p2.getX() - p1.getX();
    double ydiff = p2.getY() - p1.getY();
    double distance = sqrt(xdiff * xdiff + yydiff * ydiff);
}
The compiler complains about a syntax error:
test.cpp:1:27: error: passing β€˜const Point’ as β€˜this’ argument discards qualifiers [-fpermissive]
1 |     double xdiff = p2.getX() - p1.getX();
  |                    ~~~~~~~^~
This somewhat cryptic message is complaining that using the member function getX() is not guaranteed to be safe on the Point that this function promises to not modify (const Point& p2). The compiler looks at that line and is not sure if it is safe to call getX(). For all it knows, that function is going to change p2! (Remember that compiler does not necessarily have all the code available as it compiles this file. It only works on one file at a time.)
How do we deal with this? Well, we could just stop using const. Then the compiler would not worry about taking a Point we promised to keep const and calling some member function that might modify the Point. But that means ignoring a power tool for avoiding bugs based on unintended changes made via references. So a better way to solve the issue is to tell the compiler which member functions are safe to use on const objects.

Subsection 17.1.2 Making Members Const

A member function can be marked const by adding the keyword after the parameter list (before the body or the semicolon):
Listing 17.1.1.
class Point {
public:
    double getX() const {
        return m_x;
    }
    double getY() const;  // defined later
...
};  // end of point class

double Point::getY() const {
    return m_y;
}
const becomes part of the prototype of the function. getX is now β€œThe getX function that is a part of Point, returns a double, and keeps the object constant”. Anywhere we write the prototype, like on line 10 to define the getY that was declared on line 6, we must use the const.

Aside

Now, when we call getX() on a const Point, the compiler knows that it is safe to do so as the function was marked const. As it compiles the getX() function itself, does not try to modify any member variables. If it does, the compiler will flag that change as an error.

Insight 17.1.1.

const is infectious in object-oriented code. Once we start using it, it becomes important to use it everywhere so the compiler knows what operations are safe.
So what do we mark const? Any functions that do not change the current object. In the version of Point below, that includes getX(), getY(), and toString(). We can’t mark the setters, shift() or the constructor as const as they all modify (or create) the object:
#include <iostream>
#include <string>
#include <format>
using namespace std;

// A point class with const used in the member function
class Point {
public:
    // Construct a point
    Point(double x, double y) {
        m_x = x;
        m_y = y;
    }

    double getX() const {
        return m_x;
    }

    double getY() const {
        return m_y;
    }

    void setX(double x) {
        m_x = x;
    }

    void setY(double y) {
        m_y = y;
    }

    void shift(double dx, double dy) {
        setX(getX() + dx);
        setY(getY() + dy);
        // or
        // m_x += dx;
        // m_y += dy;
    }

    string toString() const {
        return format("({}, {})", getX(), getY());
    }

private:
    double m_x, m_y;
};
Now we can successfully write a function that tries to use a const Point reference:
Listing 17.1.2.
Note that it is still not OK to use non-const functions. If you add a line p1.setX(10) to the distanceBetween code, it will produce an error. You are now calling a function that is not known to be safe on a Point we promised to keep const.

Checkpoint 17.1.1.

Imagine a class Person. Which of the following functions sound like they should be const?
  • getName
  • This sounds like a simple getter. It should not change the object, so it should be const.
  • setAge
  • No, this sounds like a setter. It should change the object, so it should not be const.
  • updateAddress
  • No, this sounds like it is going to modify the object, so it should not be const.
  • isAdult
  • This sounds like it should not change the object, so it should be const.
  • A print that returns a String representation.
  • This sounds like it should not change the object, so it should be const.

Checkpoint 17.1.2.

You have attempted of activities on this page.