PL

What Is const in C++

This article reviews three common uses of const in C++, analyzes its semantic confusion and limitations, and discusses alternatives such as constexpr.

Published

const in C++ is a concept that is very easy to confuse. I think this is mainly because the design of const is not complete enough. Although the constexpr keyword newly added in C++11 has solved part of the problem, many essential problems still have not been resolved; in fact, many people have not even recognized the essence of these problems. Here I will explain my understanding of const in C++, try to analyze the essential problems it solves, and, for some of those problems, provide a few alternative approaches.

Three Uses of const in C++

const in C++ has three uses:

  1. Modifying a variable (type) to indicate that the variable (type) cannot be modified

  2. Modifying a variable (type) to indicate that it is a constant

  3. Modifying a member function to indicate that the member function will not modify the class’s internal state

Of these three uses, the second has already been replaced by the new keyword constexpr, while the first and second uses have more or less some problems.

const Indicates That a Variable (Type) Cannot Be Modified

Code example:

const int a = f();
cout << a << endl; // OK
a = 5; // Compile Error! `a` is readonly.

In fact, when people use this form, they only want to indicate that the modified variable a is read-only. However, C++ has a problem here: const does not modify the variable; it modifies the variable’s type. Here, the type of variable a is int const, and this type is incompatible with int. This often leads to some extremely ill-advised situations:

  1. Setting a function’s return value type to a const type

  2. Setting a function’s parameter type to a const type, only to later discover that a deeply nested dependent function needs to modify this value

In the first case, when modifying an object on the stack, this is a completely pointless practice, because a copy when the function returns can remove the const modifier, which is equivalent to doing nothing. When modifying a pointer (including reference) type, more consideration is required to avoid short-sighted behavior like the second case.

The reason C++ does this is that an object may be on the stack or on the heap, and these two cases are not transparent to the user. The user must know where the object is and reflect that difference in the code, which is quite painful for C++ programmers. When an object is on the heap, although we may prohibit modifying the pointer that points to this object instance, it is still possible to modify the contents of the object instance it points to. To prevent this from happening, C++ was designed so that const modifies types. In this way, when we use a read-only type, the contents of the object instance cannot be modified whether it is on the stack or on the heap.

const Modifies Member Functions

Example code:

class A
{
private:
    int m_id;
public:
    int get_id(void) const;
    void set_id(int id);
}

This problem is highly related to the previous one. Precisely because const modifies the type when representing read-only semantics, it becomes necessary to mark which member functions are read-only. These member functions can continue to be used on read-only types, while other member functions cannot.

At this point, we suddenly realize that this use of const is actually defining a new type: the read-only type X const of type X.

This is also why many programmers are reluctant to mark member functions with const.

The readonly Keyword in C#

C# has two keywords, readonly and const. The former is used to modify a variable, indicating that the variable is read-only. The latter is also used to modify a variable, indicating that the variable is a compile-time constant. I think this is a relatively good design, because in this design, the two keywords represent two completely different semantics, and the semantics of readonly match people’s expectation that they are modifying a variable rather than its type.

readonly can only control that objects on the stack (including registers) are not modified, such as variables of type int or object references themselves. If you want to control that variables on the heap are not modified, you need to define a new read-only interface for that type. Only by using this read-only interface type can you control that the heap variable itself is not modified.

Example code

interface AReadOnly {
    int GetId();
}
class A {
    private int m_id;
    public GetId();
    public SetId(int id);
}

AReadOnly a_const = new A();