Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 7

Using `const` Correctly

Recommended resources

Hello! Welcome to your seventh lesson in the "C++ for ESP32" course.

Introduction

In our last lesson, we distinguished between stack and heap memory, establishing a key principle for embedded systems: prefer the fast, automatic memory of the stack whenever possible. We saw that variables can live in different memory regions and have different lifetimes.

Today, we'll build on that by learning how to protect those variables from unintended changes. The learning outcome for this lesson is to apply the const keyword to variables, pointers, and member functions.

The const keyword is a promise you make to the compiler—and to other programmers reading your code—that a piece of data will not be modified. Using const correctly, a practice known as "const correctness," is a hallmark of professional C++ code. It allows the compiler to catch a whole class of bugs at compile time, makes your code's intent clearer, and can even open up opportunities for optimization. For resource-constrained devices like the ESP32, this level of compile-time safety is invaluable.


1. The Basics: Constant Variables

The most straightforward use of const is to declare a variable whose value cannot be changed after it's initialized. Think of it as defining a named constant rather than a variable.

// This is a compile-time constant. Its value cannot be changed.
const int SENSOR_PIN = 34; 

// This is also a constant.
const float PI = 3.14159;

// Attempting to change it will result in a compiler error.
// SENSOR_PIN = 35; // ERROR: assignment of read-only variable 'SENSOR_PIN'

This is superior to using #define for constants because const variables are type-safe and respect scope, which helps prevent subtle bugs.

CONST in C++

Let's start with a video from The Cherno that introduces the fundamental idea behind the const keyword and its simplest application.

Please watch from the beginning to 02:15. This section explains the purpose of const and demonstrates its basic use with variables.


2. The Tricky Part: const and Pointers

When const is used with pointers, its meaning depends on its position. This is a common point of confusion, but it's essential for understanding how to work with data safely. There are three combinations to learn.

To make sense of the syntax, a helpful trick is to read the declaration from right to left.

  1. Pointer to a Constant Value (const int*)

    • Declaration: const int* ptr; (or int const* ptr; — they are equivalent).
    • Read it: "ptr is a pointer to an integer that is constant."
    • Meaning: You cannot change the value at the memory location the pointer points to through this pointer. You can, however, change where the pointer itself points.
    int x = 10;
    int y = 20;
    const int* ptr = &x;
    
    // *ptr = 15; // ERROR: You promised not to change the value it points to.
    ptr = &y;    // OK: You can change which address the pointer holds.
    
  2. Constant Pointer to a Value (int* const)

    • Declaration: int* const ptr = &some_int; (Must be initialized).
    • Read it: "ptr is a constant pointer to an integer."
    • Meaning: You cannot change the address the pointer holds (i.e., you can't make it point to something else). You can, however, change the value at the location it points to.
    int x = 10;
    int y = 20;
    int* const ptr = &x;
    
    *ptr = 15; // OK: The value at the address can be changed.
    // ptr = &y;   // ERROR: You promised the pointer itself would not change.
    
  3. Constant Pointer to a Constant Value (const int* const)

    • Declaration: const int* const ptr = &some_int; (Must be initialized).
    • Read it: "ptr is a constant pointer to an integer that is constant."
    • Meaning: You can change neither the pointer itself nor the value it points to. It's locked down completely.
    int x = 10;
    const int* const ptr = &x;
    
    // *ptr = 15; // ERROR: The value is constant.
    // ptr = &y;   // ERROR: The pointer is constant.
    

This is a dense topic, so let's watch a detailed explanation.

CONST in C++

The same video from The Cherno provides an excellent breakdown of these three pointer scenarios.

Watch from 02:15 to 05:48. Pay close attention to the code examples for each of the three types of const pointers.

To reinforce this, you can also review the "Const Keyword With Pointer Variables" section in the GeeksForGeeks article, which provides clear code examples for each case.


3. const Correctness: Member Functions and Objects

The concept of const extends powerfully to classes and objects. This is where the idea of "const correctness" really comes into play.

const Member Functions

You can declare a member function as const. This is a promise that calling this function will not change the observable state of the object.

class TemperatureSensor {
private:
    int sensorPin;
    float lastReading;

public:
    TemperatureSensor(int pin) : sensorPin(pin), lastReading(0.0) {}

    // This is a const member function. It promises not to change
    // sensorPin or lastReading.
    float readValue() const {
        // lastReading = analogRead(sensorPin); // ERROR! Cannot modify a member variable.
        return analogRead(sensorPin) * 0.1; // OK, just reading.
    }

    void calibrate() {
        // This is NOT a const function, because it modifies the object's state.
        // ... calibration logic that might change internal variables ...
    }
};

The const keyword goes after the function's parameter list.

Why is this so important?

Because it determines what you can do with const objects. A const object can only call const member functions.

This is extremely relevant to what we learned about references. A common and efficient pattern is to pass large objects to functions by const reference to avoid making a copy.

void printTemperature(const TemperatureSensor& sensor) {
    // 'sensor' is a const reference. We are promising not to change it.
    
    float temp = sensor.readValue(); // OK, because readValue() is a const function.
    // sensor.calibrate(); // ERROR! Cannot call a non-const function on a const reference.
    
    std::cout << "Temperature: " << temp << std::endl;
}

If readValue() were not marked const, the compiler would assume it might change the object, and the call inside printTemperature would be forbidden, generating a compile-time error.

Const correctness in C++

This video introduces the philosophy of 'const correctness' using a real-world analogy, which helps frame why these rules are so beneficial.

Watch the following segments: Introduction (00:00 - 01:44): Understand the core idea behind const correctness. Rule 1: Const References (01:44 - 03:17): This directly connects to our use of const references for function parameters. Rules 3 & 4: Const Getters and Methods (04:10 - 06:32): This explains the need to mark member functions as const and the common errors beginners face.

The mutable Keyword

What if a const member function needs to change a member variable for a legitimate, non-observable reason, like caching a computed value or for debugging? The mutable keyword provides a specific exception. A member variable declared mutable can be modified inside a const member function. Use it sparingly.

CONST in C++

The Cherno briefly explains the mutable keyword as an escape hatch from const's restrictions.

Watch from 11:08 to 12:01 to see how and why mutable is used.


4. Best Practices and Common Pitfalls

Applying const consistently is a discipline. Here are some key guidelines to follow, which will help you write safer and more professional code.

The C++ const Keyword: A Practical Guide

This article, "The C++ const Keyword: A Practical Guide", has an excellent section summarizing common mistakes. Reading this will help you avoid them in your own code.

Find and read the section titled "Common Pitfalls and How to Avoid Them". It clearly lists five key points to remember.

In summary, the key rules of const correctness are:

  1. Use const for variables that shouldn't change.
  2. Pass objects by const reference when the function doesn't need to modify them.
  3. Mark any member function that does not modify the object's state as const.
  4. Be very careful with const_cast—it's usually a sign of a design problem.
  5. Understand the difference between a const pointer and a pointer-to-const.

Conclusion

Mastering const is a significant step toward writing professional C++ code. It's not just about adding a keyword; it's about designing your code to be robust, clearly communicating your intentions, and leveraging the compiler as a safety net.

Key Takeaways:

  • const variables must be initialized and cannot be changed.
  • The position of const with pointers matters: it can make the data constant, the pointer constant, or both.
  • const member functions promise not to modify the object's state and are the only functions that can be called on const objects or const references.
  • Adhering to "const correctness" prevents a wide range of bugs by enforcing your design intentions at compile time.

In our next lesson, we will explore basic inheritance, a fundamental pillar of object-oriented programming that allows you to create new classes that reuse, extend, and modify the behavior of existing ones. We'll see that const correctness plays an important role there as well.