const and volatile Qualifiers
CV-qualifiers (const and volatile) modify type behavior. const prevents modification; volatile prevents compiler optimization.
const is About Contract
const is a compile-time contract stating "this value won't change" - enabling optimization and catching bugs early.
const Qualifier
Basic const
const int x = 42;
x = 10; // ❌ Error: cannot modify const
int y = 100;
y = 200; // ✅ OK: not const
const Variables Must Initialize
const int x; // ❌ Error: uninitialized const
const int y = 42; // ✅ OK
const with Pointers
// Pointer to const (cannot modify value)
const int* ptr1 = &x;
*ptr1 = 10; // ❌ Error
ptr1 = &y; // ✅ OK: can change pointer
// Const pointer (cannot change pointer)
int* const ptr2 = &x;
*ptr2 = 10; // ✅ OK: can modify value
ptr2 = &y; // ❌ Error: cannot change pointer
// Const pointer to const (both const)
const int* const ptr3 = &x;
*ptr3 = 10; // ❌ Error
ptr3 = &y; // ❌ Error
// Read right-to-left
const int* ptr; // ptr is pointer to const int
int const* ptr; // Same as above
int* const ptr; // ptr is const pointer to int
const int* const ptr; // ptr is const pointer to const int
const with References
int x = 42;
// const reference (cannot modify through reference)
const int& ref = x;
ref = 10; // ❌ Error
x = 10; // ✅ OK: x itself can change
// const reference can bind to temporary
const int& ref2 = 42; // ✅ OK: lifetime extended
int& ref3 = 42; // ❌ Error: non-const ref to temporary
const Member Functions
class Counter {
int count;
public:
Counter() : count(0) {}
// const member function (doesn't modify object)
int getCount() const {
// count++; // ❌ Error: modifies member
return count;
}
// Non-const member function
void increment() {
count++; // ✅ OK
}
};
const Counter c;
c.getCount(); // ✅ OK: const function
c.increment(); // ❌ Error: non-const function on const object
mutable (Override const)
class Cache {
mutable int access_count; // Can modify even in const functions
std::string data;
public:
std::string getData() const {
access_count++; // ✅ OK: mutable
return data;
}
};
constexpr (C++11)
Compile-time constants and functions:
constexpr int square(int x) {
return x * x;
}
constexpr int value = square(5); // Evaluated at compile-time
int arr[value]; // OK: value is compile-time constant
const vs constexpr:
const int x = 10; // Runtime or compile-time
constexpr int y = 10; // Must be compile-time
const int cx = getValue(); // ✅ OK: runtime const
constexpr int cy = getValue(); // ❌ Error: must be compile-time
volatile Qualifier
Prevents compiler optimization - value can change externally:
volatile int* hardware_register = (volatile int*)0x40000000;
// Compiler won't optimize away reads
int x = *hardware_register;
int y = *hardware_register; // Second read actually happens
// Without volatile, compiler might optimize to:
// int x = *hardware_register;
// int y = x; // Reuse cached value
Common Uses
// 1. Memory-mapped I/O
volatile uint32_t* GPIO = (volatile uint32_t*)0x40020000;
*GPIO = 0xFF; // Write to hardware
// 2. Multi-threaded shared data (pre-C++11, now use atomics)
volatile bool flag = false; // Can change in another thread
// 3. Signal handlers
volatile sig_atomic_t signal_received = 0;
volatile is NOT for Threading
In modern C++, use std::atomic for thread-safe variables, not volatile.
const Correctness
Practice of using const wherever possible:
// Good const correctness
class String {
char* data;
size_t length;
public:
// const getters
size_t size() const { return length; }
char at(size_t i) const { return data[i]; }
// const parameters
void append(const String& other) {
// ...
}
// const return (for immutable access)
const char* c_str() const { return data; }
};
Benefits
void process(const std::vector<int>& data) { // Won't modify
// data.push_back(5); // ❌ Error: const
for (int x : data) { // OK to read
std::cout << x;
}
}
// Accepts temporaries
process({1, 2, 3}); // OK: binds to const&
Top-Level vs Low-Level const
const int x = 42; // Top-level const (x itself)
const int* ptr = &x; // Low-level const (pointed-to value)
int* const ptr2 = &y; // Top-level const (pointer itself)
const int* const ptr3 = &x;// Both
// Copying ignores top-level const
const int a = 10;
int b = a; // OK: copies value, b is non-const
// Low-level const must match
const int* p1 = &a;
int* p2 = p1; // ❌ Error: discards const
Summary
const:
- Prevents modification
- Enables optimization
const int* ptr= pointer to constint* const ptr= const pointer- const member functions don't modify object
- constexpr = compile-time const
volatile:
- Prevents optimization
- For hardware registers, old threading
- Modern code uses
std::atomicinstead
Best practices:
- Use
constby default - Pass large objects as
const& - Mark non-modifying members
const - Use
constexprfor compile-time values
// Good const usage
void process(const std::string& input) {
// Read-only access
}
class Widget {
public:
int getValue() const { return value; } // const method
private:
int value;
};