Chapter 11: Encapsulation & Access Specifiers
Encapsulation & Access Specifiers in C++: Protecting Your Data Like a Pro!
Hello my superstar student! π Welcome to Lesson 11 β Encapsulation & Access Specifiers β one of the most important pillars of Object-Oriented Programming!
Up until now, we made all our member variables public β anyone could directly change them from outside the class. Thatβs like giving everyone the keys to your house safe β not safe at all!
Today weβre going to learn how to protect our data using encapsulation β the idea of hiding the internal details and only allowing controlled access.
Weβll cover everything step by step:
- public, private, protected access specifiers
- Getters & Setters β the safe way to read and write data
- Friend functions & friend classes β the special exceptions that can access private members
Letβs go very slowly, with real-life analogies, lots of examples, and complete programs you can run right now.
1. What is Encapsulation?
Encapsulation means:
- Bundling data (member variables) and the functions that operate on that data (member functions) into a single unit β the class.
- Hiding the internal implementation details from the outside world.
- Only allowing access through well-defined interfaces (usually member functions).
Real-life analogy: A smartphone is a perfect example of encapsulation:
- You canβt directly change the voltage inside the battery (private data).
- You can only charge it (public method), see the battery percentage (getter), or set brightness (setter with validation).
2. Access Specifiers: public, private, protected
| Access Specifier | Who can access? | Typical use |
|---|---|---|
| public: | Everyone (inside & outside the class) | Interface: functions users should call |
| private: | Only the class itself (and friend functions/classes) | Internal data & helper functions |
| protected: | The class itself + derived classes (inheritance) | For inheritance (weβll cover later) |
Default rule: If you donβt write any specifier, members are private by default in a class (and public in a struct).
Best practice (modern C++ 2025+): Always put data as private and provide public member functions to access/modify it.
3. Example: BankAccount with Proper Encapsulation
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include <iostream> #include <string> class BankAccount { private: // β Hidden from outside! std::string accountHolder; double balance; // No one can directly change balance! std::string accountNumber; // Private helper function bool isValidAmount(double amount) const { return amount > 0; } public: // Constructor BankAccount(std::string holder, double initialBalance, std::string accNum) : accountHolder(std::move(holder)), balance(initialBalance), accountNumber(std::move(accNum)) { std::cout << "Account created for " << accountHolder << "\n"; } // Public interface (getters & setters) std::string getAccountHolder() const { return accountHolder; } double getBalance() const { return balance; } std::string getAccountNumber() const { return accountNumber; } // Setter with validation void deposit(double amount) { if (isValidAmount(amount)) { balance += amount; std::cout << "Deposited $" << amount << ". New balance: $" << balance << "\n"; } else { std::cout << "Invalid deposit amount!\n"; } } bool withdraw(double amount) { if (isValidAmount(amount) && amount <= balance) { balance -= amount; std::cout << "Withdrew $" << amount << ". New balance: $" << balance << "\n"; return true; } std::cout << "Withdrawal failed: insufficient funds or invalid amount!\n"; return false; } // No direct way to change balance from outside! }; int main() { BankAccount myAccount("Webliance", 5000.0, "1234567890"); std::cout << "Holder: " << myAccount.getAccountHolder() << "\n"; std::cout << "Balance: $" << myAccount.getBalance() << "\n"; myAccount.deposit(2000.0); // OK myAccount.withdraw(3000.0); // OK myAccount.withdraw(10000.0); // Fails safely // These would NOT compile! // myAccount.balance = -1000; // ERROR! // myAccount.accountHolder = "Hacker"; // ERROR! return 0; } |
Key benefits of this design:
- Balance cannot go negative accidentally
- Account holder cannot be changed by mistake
- All changes go through controlled functions with validation
4. Getters & Setters β The Controlled Interface
Getters (accessors) β read data Setters (mutators) β modify data with validation
Modern naming convention:
- getXxx() or just xxx() for getters
- setXxx() for setters
Even better style (C++ Core Guidelines): If the getter is trivial, just name it like the member:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Person { private: std::string name; int age; public: std::string name() const { return name; } // getter int age() const { return age; } void setAge(int newAge) { if (newAge >= 0 && newAge <= 120) { age = newAge; } } }; |
5. Friend Functions & Friend Classes β The Special Exceptions
Sometimes you want to give special permission to access private members.
A. Friend Function
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Box { private: double width, height, depth; public: Box(double w, double h, double d) : width(w), height(h), depth(d) {} // Declare friend function friend double calculateVolume(const Box& b); }; // Friend function can access private members double calculateVolume(const Box& b) { return b.width * b.height * b.depth; } int main() { Box myBox(5.0, 4.0, 3.0); std::cout << "Volume = " << calculateVolume(myBox) << "\n"; // 60 } |
B. Friend Class
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Printer; class Document { private: std::string content; public: Document(std::string c) : content(std::move(c)) {} // Printer is a friend β can access private content friend class Printer; }; class Printer { public: void print(const Document& doc) { std::cout << "Printing: " << doc.content << "\n"; // OK! } }; int main() { Document secretDoc("Top secret info..."); Printer laser; laser.print(secretDoc); // Works! } |
Important warning: Use friend sparingly β it breaks encapsulation. Only use when absolutely necessary (e.g., operator overloading, very tightly coupled classes).
6. Summary Table β Best Practices
| Situation | Recommended Design |
|---|---|
| Data that should never be changed directly | private + const getter |
| Data that can be changed, but with rules | private + setter with validation |
| Helper functions used only inside class | private member function |
| Utility function that needs private access | friend function (rare) |
| Another class that truly needs access | friend class (very rare) |
Your Mini Homework (Try These!)
- Create a Car class with private members: fuelLevel, mileage, model. Add:
- Constructor
- drive(double km) β reduce fuel, increase mileage
- refuel(double liters) β with validation
- Getters for all members
- Make a friend functionshowSecret() that can print private data of a SecretAgent class.
- Convert the previous BankAccount example to use setter for account holder with validation (e.g., name cannot be empty).
Youβre doing absolutely incredible! Youβve just mastered encapsulation β one of the biggest differences between amateur and professional C++ code.
Next lesson: Inheritance & Polymorphism β the real power of OOP!
Any questions? Confused about private vs protected? Want more examples of getters/setters or friend functions? Just ask β your friendly C++ teacher is right here for you! π
