C++: OOP, Encapsulation
Learn how to hide data inside a class, expose it through a controlled interface, and use validation to keep objects in a valid state at all times.
What is Encapsulation?
Encapsulation is the practice of keeping an object's internal data private and exposing only a carefully chosen public interface. External code cannot reach in and corrupt the data directly, it must go through the methods the class provides. This means the class can validate every change and guarantee that its state is always meaningful.
Encapsulation in One Sentence
Hide the data, expose the behavior. Let the object manage its own state through methods rather than letting anyone write to it freely.
- Data members are private: no outside code can read or write them directly
- Public methods form the interface: they validate input before changing state
- The internal representation can change without breaking callers, as long as the public interface stays the same
- Think of an ATM: you interact with buttons and a screen, not the internal cash mechanism
Data Hiding
Data hiding means declaring member variables private so they are invisible to code outside the class. Any attempt to access them directly from outside produces a compile error, the problem is caught before the program ever runs.
private: the data hiding keyword
A private member is accessible only inside the class body. Nothing outside, not main(), not another class, can touch it without going through a public method.
- Declared with the private: access specifier (also the default for class members)
- Direct outside access is a compile error: obj.balance = -999; will not compile
- Forces callers to use the methods you provide, where you control what is allowed
- If there is no setter for a private member, it is effectively read-only from outside
Data Hiding
C++balance is private. The only way out is through the public getter.
Getter and Setter Methods
A getter reads a private member and returns its value. A setter accepts a new value, validates it, and then writes to the private member. Together they form a controlled gateway: the object decides what values are acceptable before anything gets stored.
Getters and Setters
Getters are almost always const because they only read state. Setters are never const because they modify it.
- Getter: double getBalance() const { return balance; }
- Setter: void setBalance(double b) { if (b >= 0) balance = b; }
- A getter with no matching setter makes the member read-only from outside
- Validation in the setter is the key benefit over direct member access
Person Class with Age Validation
C++The setter rejects negative values and unrealistic ages. The constructor routes through the same setter so creation is also validated.
BankAccount: Deposit and Withdraw
A bank account is the classic encapsulation example. The balance is private, nobody can write to it directly. Instead, callers use deposit() and withdraw(), which enforce business rules: no negative deposits, no overdrafts.
BankAccount Encapsulation
Every change to balance flows through a method that checks the rules. There is no way for outside code to bypass those checks.
- private: balance, the core protected state
- deposit(amount): accepts only positive amounts before adding to balance
- withdraw(amount): rejects the operation if funds are insufficient
- getBalance() const: read-only view, no setter provided, so balance can only change through the two controlled methods
BankAccount with deposit and withdraw
C++balance is unreachable from outside. Every change goes through a method that enforces the rules.
Hiding the Internal Representation
Encapsulation also lets you store data in one format internally while presenting a different interface to callers. The Temperature class stores a single Celsius value but exposes both a Celsius and a Fahrenheit getter. The caller never needs to know how the data is stored.
Single Storage, Multiple Views
Store once, expose in any format. Changing the internal storage later (say, to Kelvin) only requires updating the class, not every caller.
- Only one member variable needed: double celsius
- getCelsius() const: returns the stored value directly
- getFahrenheit() const: converts on the fly with (celsius * 9.0/5.0) + 32
- setCelsius(double c): validates that the value is above absolute zero before storing
Temperature: Celsius Storage, Fahrenheit View
C++One private value, two public getters. The conversion happens inside the class, invisible to the caller.
Read-Never Data: The Password Class
Some private members should never be readable from outside, only verifiable. The Password class stores a password privately and provides no getter at all. External code can only ask "is this the correct password?" through a validate() method that returns true or false.
No Getter: Write-Only Private Data
Providing no getter makes a private member completely unreadable from outside. The only interaction is through methods that answer a specific question without exposing the raw value.
- password is private with no getter: external code can never read the stored value
- setPassword(string): enforces a minimum length rule before accepting the new value
- validate(string attempt) const: returns true only if attempt matches the stored password
- This pattern mirrors real authentication systems where passwords are never returned, only checked
Password Class
C++No getter exists. The only operation is validate(), which answers true or false without revealing the stored value.
Access Control Summary
The three access specifiers control exactly how much of a class is visible to the outside world. Good encapsulation practice is to default to the most restrictive level and only loosen when there is a clear reason.
Private vs Public Members
Default to private for data, public for the interface. Protected is reserved for members that derived classes need to access directly.
- private: data members, internal helper functions
- public: constructors, getters, setters, and all methods the caller needs
- protected: members that subclasses need to read or write directly
- If you are unsure, start with private and loosen only when you have a concrete need
| What to put here | private | public |
|---|---|---|
| Data members (fields) | Yes, almost always | Only for simple data holders (struct-style) |
| Constructors | Only for singletons | Yes, callers need to create objects |
| Getters and setters | No | Yes, controlled read/write interface |
| Internal helper functions | Yes | No |
| Business logic methods (deposit, withdraw) | No | Yes |
Encapsulation Best Practices
Well-encapsulated classes are easier to test, safer to change, and clearer to read. These practices distill the most common patterns that lead to clean, robust class designs.
Best Practices
Apply these consistently and your classes will remain valid, maintainable, and straightforward to use.
- Default to private: declare all data members private unless you have a specific reason not to
- Validate in setters: never trust input from outside the class, check it before storing
- Mark getters const: every read-only method should carry the const qualifier
- Route constructors through setters: call setAge(a) in the constructor body so validation runs at creation time too
- Omit setters for read-only data: if a member should not change after construction, provide only a getter
- Keep the public interface minimal: expose only what callers genuinely need, every public method is a commitment you must maintain
Knowledge Check
1. What is the core idea of encapsulation?
2. Why should data members typically be declared private?
3. What is the purpose of a setter function?
4. A getter function should almost always be marked const because:
5. In the BankAccount example, why is balance private?
6. What happens if you try to read a private member directly from outside the class?
7. Which encapsulation practice makes a member effectively read-only from outside the class?
8. The Temperature class stores only Celsius internally but offers a getFahrenheit() getter. This is an example of: