Const is a contract the compiler checks; const-correct interfaces are safer, clearer, and required to work with const objects.
const says "this won't change", and the compiler holds you to it. It appears in three main places.
Const variables. const int max_items = 10; can't be modified after initialization. Use it by default for values you don't intend to change; it makes code easier to reason about. (For values known at compile time, constexpr is even stronger; see the Advanced lessons.)
Const references and pointers in parameters. void report(const Inventory& inv) promises the caller that inv won't be modified, and it accepts temporaries and const objects. For pointers, read right to left: const int* p is a pointer to a const int (can't change *p, can repoint p), while int* const p is a const pointer to a mutable int.
Const member functions. std::size_t count() const; means the function doesn't modify the object. Only const member functions can be called on a const object or through a const reference. That makes const contagious in a good way: if count() forgets its const, nobody holding a const Inventory& can call it. Mark every member function that doesn't change observable state as const.
Getters often come in a pair, a const version returning const T& and a non-const version returning T&, as std::vector::operator[] does.
Sometimes a const function needs to update something that isn't part of the object's logical state: a cache, a statistics counter or a mutex. Declare such members mutable. Use it sparingly, and remember a const function may be called from several threads at once, so mutable state needs synchronization.
Avoid const_cast to strip const; modifying an object that was defined const is undefined behaviour.
Example
#include <iostream>
#include <string>
#include <utility>
#include <vector>
class Inventory {
public:
void add(std::string item) { items_.push_back(std::move(item)); } // modifies: not const
std::size_t count() const { return items_.size(); } // read-only
bool has(const std::string& name) const {
++lookups_; // allowed: lookups_ is mutable
for (const auto& i : items_) if (i == name) return true;
return false;
}
int lookups() const { return lookups_; }
private:
std::vector<std::string> items_;
mutable int lookups_ = 0; // statistics, not logical state
};
void report(const Inventory& inv) { // promises not to modify inv
std::cout << inv.count() << ' ' << inv.has("rope") << '\n';
// inv.add("x"); // error: add() is not const
}
int main() {
const int max_items = 10;
Inventory inv;
inv.add("rope");
report(inv); // 1 1
std::cout << max_items - inv.count() << ' ' << inv.lookups() << '\n'; // 9 1
}Remember
- Mark every member function that doesn't change the object
const const int*= pointer to const;int* const= const pointermutableis for caches and counters, not for real state
Watch out for
- Forgetting
conston a getter, which makes it uncallable through everyconst&in the codebase.