Skip to content

Encapsulation

Encapsulation is the bundling of data and methods that operate on that data within a single unit (class). It also restricts direct access to some of an object’s components, which is a means of preventing unintended interference.

┌─────────────────────────────────────────────────────────────┐
│ Encapsulation Concept │
├─────────────────────────────────────────────────────────────┤
│ │
│ Without Encapsulation With Encapsulation │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Account │ │ Account │ │
│ ├─────────────┤ ├─────────────┤ │
│ │ balance: 100│ │ - balance │ ← private │
│ ├─────────────┤ │ + getBalance│ ← public │
│ │ withdraw() │ │ + deposit() │ │
│ │ deposit() │ │ + withdraw()│ │
│ └─────────────┘ └─────────────┘ │
│ │
│ Anyone can modify Controlled access through │
│ balance directly public methods │
│ │
└─────────────────────────────────────────────────────────────┘

Only accessible within the same class:

class BankAccount {
private:
double balance; // Only BankAccount can access
public:
void deposit(double amount) {
if (amount > 0) {
balance += amount; // OK - same class
}
}
};
int main() {
BankAccount account;
// account.balance = 1000000; // Error! Private
account.deposit(100); // OK - through public method
}

Accessible in the class and derived classes:

class Animal {
protected:
std::string name; // Accessible in Animal and subclasses
public:
Animal(const std::string& n) : name(n) {}
};
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
void displayName() {
std::cout << name << std::endl; // OK - derived class
}
};

Accessible everywhere:

class Rectangle {
public:
double width;
double height;
};
int main() {
Rectangle r;
r.width = 5; // OK - public
r.height = 3; // OK - public
}

Control access to private data:

class Person {
private:
std::string name;
int age;
public:
// Getters
std::string getName() const { return name; }
int getAge() const { return age; }
// Setters with validation
void setName(const std::string& n) {
if (!n.empty()) {
name = n;
}
}
void setAge(int a) {
if (a >= 0 && a <= 150) {
age = a;
}
}
};
// Without encapsulation - unsafe
class Account {
public:
double balance;
};
Account acc;
acc.balance = -1000000; // Invalid! No validation
// With encapsulation - safe
class Account {
private:
double balance = 0;
public:
void deposit(double amount) {
if (amount > 0) { // Validation
balance += amount;
}
}
};
class List {
private:
// Could be array, linked list, vector, etc.
// Change implementation without breaking user code
std::vector<int> data;
public:
void add(int value) {
data.push_back(value); // Implementation detail
}
};
class Temperature {
private:
double celsius;
public:
double getCelsius() const { return celsius; }
// If we change to store Fahrenheit internally,
// only this method needs to change
double getFahrenheit() const {
return celsius * 9/5 + 32;
}
};

The Uniform Initialization Principle (C++11)

Section titled “The Uniform Initialization Principle (C++11)”
class Point {
private:
double x, y;
public:
// Constructors
Point() : x(0), y(0) {}
Point(double xVal, double yVal) : x(xVal), y(yVal) {}
// Getter with const correctness
double getX() const { return x; }
double getY() const { return y; }
};

Mark methods that don’t modify object state:

class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// Won't modify any member variables
double getRadius() const { return radius; }
// Will modify member - can't be const
void setRadius(double r) { radius = r; }
double area() const {
// radius = 10; // Error! Can't modify in const function
return 3.14159 * radius * radius;
}
};

Complete Example: Bank Account with Encapsulation

Section titled “Complete Example: Bank Account with Encapsulation”
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
class InsufficientFundsException : public std::exception {
public:
const char* what() const noexcept override {
return "Insufficient funds for this operation";
}
};
class InvalidAmountException : public std::exception {
public:
const char* what() const noexcept override {
return "Invalid amount provided";
}
};
class BankAccount {
private:
// Private data - hidden from outside
std::string accountNumber;
std::string ownerName;
double balance;
std::vector<std::string> transactionHistory;
// Private helper methods
void addToHistory(const std::string& transaction) {
transactionHistory.push_back(transaction);
}
public:
// Constructor
BankAccount(const std::string& accNum, const std::string& owner)
: accountNumber(accNum), ownerName(owner), balance(0) {}
// Public interface - controlled access
// Getters (read-only)
std::string getAccountNumber() const { return accountNumber; }
std::string getOwnerName() const { return ownerName; }
double getBalance() const { return balance; }
// Transaction methods with validation
void deposit(double amount) {
if (amount <= 0) {
throw InvalidAmountException();
}
balance += amount;
addToHistory("Deposit: $" + std::to_string(amount));
}
void withdraw(double amount) {
if (amount <= 0) {
throw InvalidAmountException();
}
if (amount > balance) {
throw InsufficientFundsException();
}
balance -= amount;
addToHistory("Withdrawal: $" + std::to_string(amount));
}
// Read-only access to history
const std::vector<std::string>& getHistory() const {
return transactionHistory;
}
// Display account info
void display() const {
std::cout << "Account: " << accountNumber << "\n";
std::cout << "Owner: " << ownerName << "\n";
std::cout << "Balance: $" << balance << "\n";
}
};
int main() {
try {
BankAccount account("123456", "John Doe");
// Deposit money (through controlled interface)
account.deposit(1000);
account.deposit(500);
// Try invalid deposit
// account.deposit(-100); // Throws exception
// Withdraw
account.withdraw(200);
// Try to withdraw too much
// account.withdraw(10000); // Throws exception
account.display();
std::cout << "\nTransaction History:\n";
for (const auto& txn : account.getHistory()) {
std::cout << txn << "\n";
}
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << "\n";
}
return 0;
}
GuidelineExample
Keep data privateprivate: double balance;
Provide public getters/setters when neededpublic: double getBalance() const;
Use const for read-only accessdouble getValue() const;
Validate input in settersvoid setAge(int a) { if (a >= 0) age = a; }
Minimize public membersOnly expose what’s necessary
  • Encapsulation bundles data and methods, restricting direct access
  • private - only accessible within the class
  • protected - accessible in class and derived classes
  • public - accessible everywhere
  • Use getters and setters for controlled access
  • Always use const for methods that don’t modify state
  • Encapsulation provides data protection, implementation flexibility, and maintainability

Now let’s learn about friend functions and operator overloading.

Next Chapter: 13_friend_functions.md - Friend Functions and Operator Overloading