Const Correctness

Writing const correct code is about more than using `const` in a few places and letting the compiler figure out if it makes sense.

There are two components about using the keyword `const` in C++ code: A syntactic component and a semantic component.

Syntactic const

The syntactic component is what the compiler figures out at compile time. It does a pretty good job at this: If we declare a variable of a builtin type, e.g. `int`, as `const`, then the compiler won’t let us modify it:

int const cantModify = 42;
cantModify = 33; //ERROR

The error message will tell us that we try to assign to a “read-only variable” (GCC) or to a variable “with const-qualified type” (Clang). The same will happen if we have a `const` instance of a struct or class and directly try to alter a data member:

struct SomeData {
  int i;
  double d;
};

SomeData const data {42, 1.61};
data.i = 55; //ERROR

Methods

Of course, the compiler doesn’t stop here. If we have a method on a class, the compiler by default assumes that it may alter the object on which we call it. We can not call those methods on `const` objects. Instead, we have to explicitly declare methods `const` to be able to call them on `const` objects.

class SomeClass {
public:
  void maybeModify();
  void dontModify() const;
};

SomeClass const someObject{};
someObject.dontModify(); //OK
someObject.maybeModify(); //ERROR

We may get slightly different error messages here, e.g. “passing ‘const SomeClass’ as ‘this’ argument discards qualifiers” for GCC and “member function ‘maybeModify’ not viable: ‘this’ argument has type ‘const SomeClass’, but function is not marked const” for Clang.

The compiler goes even further. When we implement a `const` method, it checks that we really don’t modify the object. Modifying member data in a `const` method will cause an error:

class SomeClass {
  int i;
public:
  void dontModify() const {
    i = 47; //ERROR
  }
};

This of course only is done for non-static data members, as static members are not part of the object and therefore can be altered without altering the object.

Limits of syntactic const

Syntactic `const` is limited in some ways. For example, if we have a `const` pointer, the pointer itself may not be altered, i.e. where it points to. However, the pointee, i.e. the object it points to, may be altered.

int i = 0; 
int j = 1;
int *const pi = &i;
*pi = 33; //OK - i is now 33
pi = &j; //ERROR - pi is const

This limit of course is also applies for smart pointers and other similar classes.

Semantic const

We can take this example further. Imagine a pointer which is member of a class. In a `const` method we can not alter the pointer, but we can alter the pointee, as explained above. Now what if the pointee is semantically part of our object?

class Car {
  unique_ptr<Engine> engine;
public:
  void lock() const {
    engine->modify(); //whoops
  }
}

We have to be careful not to accidentally modify objects should be semantically constant but are not syntactically `const`. This becomes even more apparent if we give back handles to parts.

Engine const& Car::getEngine() const {
  return *engine;
}

The first `const` here is important, otherwise we would allow user of our class to modify parts of constant objects, which is not a good idea. You can observe this behavior in standard container classes, e.g. for a  `vector<T> const tVec`, the access operator `tVec[0]` returns a `T const&`, although internally the `vector` only has a pointer to its data.

Not const enough

These examples are rather straight forward. But what if the pointer does not refer to a semantic part of out object but rather to another part of a common larger structure? Consider a binary tree, where each node has a `parent` pointer and two child pointers `left` and `right`.

We could now write getters for those other nodes. Should they return references or pointers to `const` or non-`const` Nodes? Should the getters themselves be marked `const`? Let’s try:

class Node {
  Node* parent;
  Node* left;
  Node* right;
public:
  Node* getParent() const;
  Node* getLeft() const;
  Node* getRight() const;
};

Node const* node = getTree();
Node* leftChild = node->getLeft();
Node* trickedYou = leftChild()->getParent();

Here `trickedYou` is a non-`const` pointer to the same `const` object as `node`, and we got there by only using `const` methods. That means the `const` was in fact a lie. We have to be careful designing our interfaces, by adding const consciously.

A bit too const

There is another case where syntactic `const` doesn’t do what we liked to. In the last example, we had to add some `const` to get the semantics right. There are cases where the exact opposite is the case, i.e. where syntactic const is just too much.

Imagine a mesh class in a 3D program. Calculating the volume of such objects might be costly. Depending on the uses we might not want to calculate the volume for every mesh when we construct or alter it, but we might want to store the result once we had to calculate it.

class Mesh {
  vector<Vertex> vertices;
  double volume;
  bool volumeCalculated;
public:
  Mesh( /* ... */ ) 
    : /* ... */ volume{0}, volumeCalculated{false}
  {}

  void change(/* ... */) { volumeCalculated = false; }

  double getVolume() const {
    if (volumeCalculated) {
      return volume;
    }
      
    volume = // calculate...   !!!
    volumeCalculated = true; //!!!
    return volume;
  }
};

This won’t compile, because we are modifying the members `volume` and `volumeCalculated` inside a `const` method. The wrong solution which sadly can be seen very often in the wild is to make `getVolume()` non-`const`. As a result, you can’t call `getVolume()` on `const` meshes, which in turn results in less meshes being declared `const`.

The right solution in many cases like this is to declare `volume` and `volumeCalculated` as `mutable`. This keyword basically states that a member may be modified by `const` member methods, which is exactly what we are doing.

Conclusion

Const correctness is more than just using `const` everywhere. It’s a part of class design, and in some cases an extra thought or two are needed to get it right.

Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

16 Comments




  1. Dalibor Frivaldsky

    I’m not sure I like the idea of “mutable” members. It is fairly hidden in the implementation of the class and is thus not apparent when reading through the public API. Looking at “double getVolume() const”, I would really expect the method to not perform any modifications. An alternative approach would be to remove the “volume” field, “volumentCalculated” field and “getVolume() const” method from the Mesh interface and introduce method “MeshWithVolume calculateVolume() const”. The “MeshWithVolume” would represent the same mesh, but it would be responsible for storing the “volume” field and would provide “double getVolume() const” method to retrieve it. Also, there would be no need for “volumeCalculated” field, as you would know the volume was calculated when you have an instance of the “MeshWithVolume” class.

    Reply
    1. Arne Mertz

      mutable members are (or should be) an implementation detail. In the example, the `volume` and `volumeClaculated` fields are not part of the class’ state and therefore not of its visible behavior. You don’t need to care about them, because whether they change or not, `getVolume` leaves the class in the same state and therefore is semantically a const operation.

      Reply
      1. Dalibor Frívaldský

        I see, thank you for clearing that up. If I understand it correctly, modifying the “mutable” member in the “getVolume() const” method will make it not thread-safe, as TX points out. Would you please consider pointing this out in the article? I think without mentioning it, it looks like you get the solution “for free”.

        Reply
  2. Alexandre Bourlon

    Interesting, as I run into the later case on a daily basis. The workaround for years was for me to declare the member mutable, as you said, but recently I started to wonder.

    Since the member is modified in a const context in a limited number of cases (declared dirty or something in a non-const method then computed in only ONE const method – some kind of getter), I’ve used const_cast on it instead of declaring it mutable. This way it can only be modified in a const method with some very explicit warnings around it, and nowhere else. What do you think are the pro and cons of such approach?

    Also, encapsulating the whole thing into a template, some kind of LazyType helper with appropriate semantic.

    Reply
  3. Gonzalo BG

    The limits of syntactic const are incomplete. You can have a pointer to a const type:

    int a = 2;
    int const * ptr = &a;
    *ptr = 3; // ERROR
    ptr = &a; // OK

    and a const pointer to a const type:

    int const*const ptr = &a;
    *ptr = 3; // ERROR
    ptr = &a; // ERROR

    Reply
  4. eraz

    Using the mutable keyword to designate variables that can be modified in const member functions was something I had not known before! There is always something new to learn from reading blogs and articles such as these, even (especially!) if they concern the basics. Thank you, Arne!

    Reply
  5. TX

    You forgot to mention that proper const correctness guarantees thread safety. Mutable members must be atomic or protected with a mutex.

    Reply
    1. Arne Mertz

      I did not forget it, I left it for another post 😉 Except for trivial cases I would not go as far as to say that it guarantees thread safety, unless you define “proper” that way. But I definitely agree const correct class design goes a long way towards that goal.

      Reply
        1. Arne Mertz

          With nontrivial I mean e.g. the case of accessing objects through const pointers described in the post. Here the thread safety is not guaranteed by the standard any more, you have to carefully design your classes in a way that makes those cases thread safe again.

          Reply
          1. TX

            One should always be careful with the design. I do appreciate that you bring up some traps that one can fall into.

            But if you do it correctly you’ll be safe. Otherwise, if you by mistake let const functions violate thread safety, it can be considered a bug.

    1. Arne Mertz

      Hi Alfredo, thanks for the link! I didn’t know that one before, will look into it.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *