Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

LConst PConst - Exploring the const Keyword in C++

5.00/5 (1 vote)
17 Jul 2023CPOL4 min read 10.1K  
Some cases when const keyword in C++ doesn't protect us from modifications at all
const qualifier does not always work the way we expect it to. In this article, we will see some cases when the const doesn't protect us from modifications at all.

The const keyword is widely used within almost every C++ program. It symbolizes the idea of a constant variable, object, function, and more, and is meant to forbid us from manipulating and changing the content of a memory location within a certain scope. However, as we learned before, “There is nothing more deceptive than an obvious fact.” (Sherlock Holmes).

Const Rules

In this article, I won’t dive deeply into the const rules, but just move briefly on const ideas I consider as crucial for the point I want to talk about.

Let’s start with the basic rules of const. The most basic const example is a constant variable:

C++
const int a = 42;

In this example, we initialize a constant integer with a value of 42. Any const variable should be initialized at the creation stage (due to the inability to initialize it later). Once the variable is initialized, there is no way to change it (Well… const_cast but it didn’t get into this article scope).

Const Location

const keyword can be written in multiple locations of the type:

C++
const int a = 5;
int const b = 6;

The result is the same, in both cases, the variables are constant. However, this ability has a great impact when pointers join the picture.

C++
int val;
const int* a = &val;

In this example, we initialize a mutable (not const) int variable named val, and a const pointer to it called a. Now, I say const because it’s only a partial const pointer, here is actually applied only to the pointer’s value, not the pointer itself, which means that we can modify this pointer to point to another variable, but we won’t be able to modify the variable content through this pointer. For example:

C++
int val;
const int* a = &val;

// *a = 5; // Won't compile. The integer content is protected by the const qualifier.

int val2;
a  = &val2; // Works just fine.

Now, this also means that we can create the pointer variable without assigning a value to it at the creation stage:

C++
// const val; // doesn't compile
const int* a; // Works.

If we want to protect the pointer, we have to change the const qualifier location. Let’s inspect the different possible locations for the const qualifier in this example:

C++
const int* a; // the int content is being protected.
int const* b; // the int content is being protected.

int val;
int* const c = &val; // the pointer is being protected, 
                     // and therefore has to be initialized at the creation stage.

We can combine the different const locations as long as they don’t repeat the meaning in the same variable:

C++
int val;
const int* const a = &val; // both the pointer and its content are protected.
int const* const b = &val; // both the pointer and its content are protected.
// const int const c;      // Won't compile because the secound const qualifier 
                           // has the same meaning of the first const qualifier.

The rule is simple if a const is the first qualifier in the type, the const will be applied to the next element of it. Otherwise, it’ll always be applied to the previous component of it.

Side note: A constant pointer equivalent is a reference variable, for example:

C++
int val;
int &a = val;
int *const b = &val;

Variables a and b here have the same meaning, they are both protecting the pointer from changes.

Class Const Member Functions

Now that we know the rules for constant variables, let’s talk about constant functions.

Constant functions are defined as functions that cannot modify a class’s variables. For example:

C++
class A {
public:
    void func() const {
        // a = 5; // Won't compile
    }

private:
    int a;
};

Everything seems great, right? Let’s inspect another case:

C++
class A {
public:
    void func() const {
        // a = &val; // Won't compile
    }

private:
    int *a;
    int val;
};

So far, everything seems just right. If a function is const, the class’s members can not be modified. It’s time to break this rule a little:

C++
class A {
public:
    A() : a(&val) {}

    void func() const {
        *a = 42; // Wait, WHAT?!
    }

private:
    int *a;
    int val;
};

I guess it’s the right time to go back to the quote, “There is nothing more deceptive than an obvious fact.” of Sherlock. The constant function only protects the last element of the class’s members’ types. So in this example, we modified a member variable content through a pointer member inside a constant member function.

Marking this function as a const function is a mistake that might lead to bugs if someone will relay on this const qualifier. This function actually performs a hidden const_cast and it shouldn’t. In order to fix this issue, the right thing would be to remove the const qualifier from this function.

Code inspection tools sometimes might advise us to add a const qualifier to this function, because the compiler allows that, but we shouldn’t. Adding a const qualifier to a class’s function should always be done only after careful consideration of whether this function modifies the class’s state/variables or not.

Let’s see an even more confusing example:

C++
class A {
public:
    A() : a(val) {}

    void func() const {
        a = 42; // Oh no...
    }

private:
    int &a;
    int val;
};

This time, we even don’t see the * when modifying the content. And it’s not even a UB! It’s totally legal by the language’s standard.

Conclusion

The title LConst PConst stands for Logical Const vs. Physical Const. We should always prefer to use the logical one over the physical one, and the compiler doesn’t help us this time.

I didn’t dive into more aspects of the const qualifier in this article, but I might do it in future articles. If you have a specific request, feel free to contact me on the dedicated channels of this blog like Discord and Telegram.

Feel free to leave your opinions in the comments section.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)