Question
In C++, what does the const keyword mean when it appears at the end of a member function declaration, as in this class?
class Foo {
public:
int Bar(int randomArg) const {
// code
}
};
How does this affect what the function can do and how it can be called?
Short Answer
A const after a C++ member function declares a const member function. It promises not to modify the observable state of the object it is called on. By the end, you will know what this promise means, why it matters, and how to write and use const-correct classes.
Concept
A member function has an implicit object: the instance before the dot.
Foo item;
item.Bar(42);
Inside Bar, C++ effectively provides access to that object through a hidden this pointer. When the function ends with const:
int Bar(int randomArg) const;
the hidden pointer behaves conceptually like this:
const Foo* const this;
Therefore, the function cannot change ordinary data members or call non-const member functions on the current object.
This is important because many functions only inspect an object: getters, formatters, comparison functions, and calculations. Marking them const documents that behavior and lets them work with const objects and references.
class Counter {
public:
int value() const {
return count;
}
void increment() {
++count;
}
private:
int count = 0;
};
value() reads count, so it is const. increment() changes count, so it cannot be const.
Mental Model
Think of an object as a library book.
- A non-const member function may write notes in the book, erase text, or add pages.
- A const member function is allowed only to read the book.
The const after the function name is a promise from the class author: “Calling this function will not change this object's normal state.”
This differs from const on a parameter. In this declaration:
int Bar(int randomArg) const;
int randomArgis a regular parameter.- The final
constapplies to the object on whichBaris called.
Syntax and Examples
The syntax places const after the parameter list and before the function body or semicolon.
class Temperature {
public:
double celsius() const {
return celsiusValue;
}
void setCelsius(double value) {
celsiusValue = value;
}
private:
double celsiusValue = 0.0;
};
A const member function can:
- Read data members.
- Use its parameters and local variables normally.
- Call other const member functions.
class Temperature {
public:
double fahrenheit() const {
return celsius() * 9.0 / 5.0 + 32.0;
}
double celsius() const {
return celsiusValue;
}
private:
double celsiusValue = 0.0;
};
It cannot modify a normal member:
class Temperature {
public:
double celsius() const {
// celsiusValue = 20.0; // Error: modifies the object
return celsiusValue;
}
private:
double celsiusValue = 0.0;
};
It also allows calls on const objects:
const Temperature roomTemperature;
double current = roomTemperature.celsius(); // Valid
// roomTemperature.setCelsius(21.0); // Error
Step by Step Execution
Consider this class and code:
class Rectangle {
public:
int area() const {
return width * height;
}
void resize(int newWidth, int newHeight) {
width = newWidth;
height = newHeight;
}
private:
int width = 4;
int height = 3;
};
int main() {
const Rectangle card;
int result = card.area();
}
Step by step:
cardis created withwidthequal to4andheightequal to3.cardis declaredconst, so code must not modify it.card.area()is allowed becausearea()is declared with a trailingconst.area()readswidthandheight.- It calculates
4 * 3and returns12. resultreceives12.
This would fail to compile:
card.resize(10, 5);
resize() can change the object, so it cannot be called on card.
Real World Use Cases
Const member functions are common whenever an object is queried without being changed.
- Getters:
user.id() const,product.price() const - Display and logging:
order.toString() const,config.print() const - Calculations:
shape.area() const,cart.total() const - Checks:
file.isOpen() const,queue.empty() const - Comparisons:
account.equals(other) const - Container access: standard-library containers provide functions such as
size() const,empty() const, andbegin() constfor read-only use.
For example, a web service may receive an application configuration as a const Config&. Handler code can read values such as a database host and timeout, but cannot accidentally alter global configuration.
Real Codebase Usage
In production C++ code, const member functions are part of const correctness: expressing which code reads data and which code changes data.
A common pattern is to pass objects by const reference when a function only needs to inspect them:
class User {
public:
const std::string& name() const {
return name_;
}
private:
std::string name_;
};
void printUser(const User& user) {
std::cout << user.name() << '\n';
}
Because printUser receives const User&, it can call only const member functions. This makes accidental changes impossible at compile time.
Classes often provide two overloads for accessors that return references:
class Document {
public:
std::string& title() {
return title_;
}
const std::string& title() const {
return title_;
}
private:
std::string title_;
};
- A non-const
Documentcan receive a writablestd::string&. - A const
Documentreceives a read-onlyconst std::string&.
This pattern preserves safety while avoiding unnecessary copies.
Common Mistakes
Trying to modify a member in a const function
class Score {
public:
void reset() const {
points = 0; // Error
}
private:
int points = 10;
};
Remove const if resetting is part of the function's purpose.
Forgetting const on a getter
class Book {
public:
int pages() { // Missing const
return pages_;
}
private:
int pages_ = 250;
};
void printPages(const Book& book) {
// book.pages(); // Error: pages() is not const
}
Add trailing const when the function only observes the object.
Confusing trailing const with a const return value
const int value() const;
These two uses are separate:
const intapplies to the returned value. For built-in values returned by value, this is usually unnecessary.- The trailing
constapplies to the current object.
Assuming const makes all reachable data immutable
A const member function cannot modify normal members directly, but it may still change an object through a non-const pointer member:
class Holder {
public:
void changePointedValue() const {
*value_ = 10; // Allowed if value_ points to non-const int
}
private:
int* value_;
};
const protects the Holder object's direct state; it does not automatically make every object it points to const.
Using mutable to bypass const without a reason
mutable permits a member to change inside const functions. Use it only for state that does not change the object's logical value, such as a cache or synchronization mechanism.
Comparisons
| Declaration | What is const? | Can it modify the current object? |
|---|---|---|
int size() const; | The member function's object (*this) | No, except mutable members |
int print(const int value); | The local parameter value | Unrelated to the current object |
const std::string& name() const; | The returned referenced string and the current object | No |
void update(); | Nothing is const | Yes |
A const and non-const member function may have the same name and parameters because their const qualification differs:
class Buffer {
public:
char& at(std::size_t index) {
return data_[index];
}
const char& at(std::size_t index) const {
return data_[index];
}
private:
std::string data_ = "hello";
};
The compiler selects the appropriate overload based on whether the Buffer object is const.
Cheat Sheet
class Example {
public:
int read() const; // Does not modify ordinary members
void write(int x); // May modify members
};
- Put
constafter()in a member function declaration. - A const member function may read members.
- It cannot assign to normal members or call non-const member functions on
*this. - A const object can call only const member functions.
- Prefer
constfor getters, checks, formatting, and calculations that do not change the object. mutablemembers may change in const functions; use this sparingly.conston a parameter and trailingconston a member function have different meanings.- In an overload pair, const objects select the
constoverload.
FAQ
Can a const member function change local variables?
Yes. Local variables belong to the function call, not to the object.
int doubled() const {
int result = value_ * 2;
return result;
}
Can a const member function call another member function?
Yes, but normally only another const member function. A non-const function might modify the object.
Does trailing const make the entire object permanently const?
No. It applies only while executing that particular member function. Non-const functions can still modify a non-const object.
Why does a getter need const?
Without it, the getter cannot be called through a const object or const reference, which is common in function parameters and APIs.
Can constructors be const?
No. Constructors initialize a new object and cannot have a trailing const qualifier.
What is a mutable member in C++?
A mutable data member may be changed even in a const member function. It is commonly used for implementation details such as cached results, not for normal object state.
Is const after a function the same as const before a return type?
No. Trailing const qualifies the member function's object. A leading const qualifies the return type.
Mini Project
Description
Build a small BankAccount class that separates read-only account operations from operations that change the balance. This reflects real code where reporting code should inspect account data without being able to modify it.
Goal
Create a class whose read-only methods work with a const BankAccount reference while deposit and withdrawal methods remain state-changing operations.
Requirements
Implement a BankAccount class with an owner name and balance.
Add a const owner() method that returns the account owner.
Add a const balance() method that returns the current balance.
Add a deposit() method that increases the balance.
Add a const canAfford() method that checks whether a requested amount is available.
Write a function that prints account details through a const BankAccount&.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.