DEV Community

Yummyzzzz
Yummyzzzz

Posted on

A Small C++ Detail That Makes APIs Much Better: const

Sometimes the difference between a confusing C++ function and a well-designed one is just a single keyword.

That keyword is:

const
Enter fullscreen mode Exit fullscreen mode

It's easy to overlook, especially when you're starting with C++.

But using const correctly can make your code easier to understand, harder to misuse, and clearer about what a function is allowed to do.

Let's look at why.

1. The problem: unclear function behavior

Imagine seeing this function declaration:

void PrintValues(std::vector<int>& values);
Enter fullscreen mode Exit fullscreen mode

What does this function do?

Does it only print the values?

Does it modify them?

Does it remove elements?

Does it sort the vector?

We can't tell from the declaration alone.

Now consider this:

void PrintValues(const std::vector<int>& values);
Enter fullscreen mode Exit fullscreen mode

This communicates something important:

The function receives the vector by reference, but it cannot modify the vector through that reference.

That makes the function's intent much clearer.

2. Why use const references?

Consider a function that calculates the sum of a vector:

#include <vector>

int CalculateSum(const std::vector<int>& values)
{
    int sum = 0;

    for (int value : values)
    {
        sum += value;
    }

    return sum;
}
Enter fullscreen mode Exit fullscreen mode

Why use:

const std::vector<int>& values
Enter fullscreen mode Exit fullscreen mode

instead of simply:

std::vector<int> values
Enter fullscreen mode Exit fullscreen mode

There are two main reasons.

First: avoiding an unnecessary copy.

Passing a vector by value creates a separate vector for the function.

Passing it by reference avoids copying the vector's elements.

Second: expressing intent.

The function only needs to read the vector, so const communicates that it isn't supposed to modify it through this parameter.

This is especially useful when designing APIs that accept large objects.

3. const can also improve class interfaces

Consider a simple class:

class Player
{
public:
    int GetHealth() const
    {
        return health;
    }

    void SetHealth(int value)
    {
        health = value;
    }

private:
    int health = 100;
};
Enter fullscreen mode Exit fullscreen mode

Notice the const after GetHealth():

int GetHealth() const
Enter fullscreen mode Exit fullscreen mode

This is a const member function.

It promises that the function won't modify the observable state of the object through the normal member-function interface.

That means it can also be called on a const instance:

const Player player;

int health = player.GetHealth();
Enter fullscreen mode Exit fullscreen mode

Without the const-qualified member function, this call would not be allowed.

4. A few common uses of const

Declaration Meaning
const int value = 10; The value cannot be modified
const int* ptr; Cannot modify the pointed-to integer through this pointer
int* const ptr; The pointer itself cannot be reassigned
const std::string& text Read-only reference to a string
void Function() const Const-qualified member function

One detail worth remembering:

const int* ptr;
Enter fullscreen mode Exit fullscreen mode

and

int* const ptr;
Enter fullscreen mode Exit fullscreen mode

are not the same thing.

In the first case, the pointer can point somewhere else, but you cannot modify the integer through it.

In the second case, the pointer cannot be reassigned, but the integer can be modified through it.

5. Does const guarantee that nothing can change?

Not necessarily.

For example, a const reference prevents modification through that particular reference, but another non-const reference or pointer might still modify the same object.

Also, const isn't a replacement for thread synchronization.

It communicates restrictions on how an object is accessed through a particular interface. It doesn't automatically make every object thread-safe.

Understanding that distinction is important.

My takeaway

I think const is one of the most useful habits to develop when writing C++.

It forces you to think about what a function actually needs to do:

  • Does it need to modify the object?
  • Does it only need to read it?
  • Does it need a copy?
  • Should this member function be callable on a const object?

These questions help you design better interfaces.

You don't need to add const everywhere just for the sake of it. Use it where it accurately describes the behavior of your code.

Question for C++ developers:

Do you use const references by default for parameters that only need to be read, or do you decide based on the type and size of the object?

Top comments (0)