Docs
README
Encapsulation in C++
Table of Contents
- •What is Encapsulation
- •Access Modifiers
- •Getters and Setters
- •Data Validation
- •Friend Functions and Classes
- •Struct vs Class
- •Best Practices
What is Encapsulation
Encapsulation is one of the four pillars of OOP:
- •Bundling data and methods that operate on that data
- •Hiding internal implementation details
- •Providing a public interface for interaction
Benefits
| Benefit | Description |
|---|---|
| Data Protection | Prevent invalid states |
| Flexibility | Change internals without breaking external code |
| Maintainability | Easier to debug and modify |
| Abstraction | Users don't need to know internals |
Without Encapsulation (Bad)
// Exposed data - anyone can break it
struct BankAccount {
double balance; // Public!
};
BankAccount acc;
acc.balance = -1000; // Invalid state!
With Encapsulation (Good)
class BankAccount {
private:
double balance; // Protected
public:
double getBalance() const { return balance; }
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false; // Validation!
}
};
Access Modifiers
Three Access Levels
class Example {
public: // Accessible everywhere
void publicMethod();
protected: // Accessible in this class and derived classes
void protectedMethod();
private: // Accessible only in this class
void privateMethod();
};
Visibility Table
| Accessor | Same Class | Derived Class | Outside |
|---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
Default Access
class MyClass {
int x; // private by default
};
struct MyStruct {
int x; // public by default
};
Multiple Sections
class Complex {
private:
double real;
double imag;
public:
Complex(double r, double i);
void display() const;
private: // Can have multiple private sections
void normalize();
public: // And multiple public sections
Complex add(const Complex& other) const;
};
Getters and Setters
Basic Pattern
class Person {
private:
string name;
int age;
public:
// Getter - returns value (const method)
string getName() const { return name; }
int getAge() const { return age; }
// Setter - sets value (with validation)
void setName(const string& n) { name = n; }
void setAge(int a) {
if (a >= 0) age = a;
}
};
Reference Getters (For Large Objects)
class DataContainer {
private:
vector<int> data;
public:
// Return by const reference (efficient, read-only)
const vector<int>& getData() const { return data; }
// Non-const reference (allows modification)
vector<int>& getData() { return data; }
};
Read-Only Properties
class Rectangle {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// Only getters - no setters (immutable after construction)
double getWidth() const { return width; }
double getHeight() const { return height; }
// Derived property - no setter possible
double getArea() const { return width * height; }
};
Computed Properties
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getRadius() const { return radius; }
void setRadius(double r) { if (r > 0) radius = r; }
// Computed from radius - not stored
double getDiameter() const { return 2 * radius; }
double getArea() const { return 3.14159 * radius * radius; }
double getCircumference() const { return 2 * 3.14159 * radius; }
};
Data Validation
Validation in Setters
class Temperature {
private:
double kelvin; // Always store in standard unit
public:
Temperature(double k = 0.0) { setKelvin(k); }
// Validate: Kelvin can't be negative
void setKelvin(double k) {
kelvin = (k >= 0) ? k : 0;
}
void setCelsius(double c) {
setKelvin(c + 273.15);
}
void setFahrenheit(double f) {
setKelvin((f - 32) * 5.0 / 9.0 + 273.15);
}
double getKelvin() const { return kelvin; }
double getCelsius() const { return kelvin - 273.15; }
double getFahrenheit() const { return (kelvin - 273.15) * 9.0 / 5.0 + 32; }
};
Validation with Exceptions
#include <stdexcept>
class Age {
private:
int value;
public:
Age(int v) { setValue(v); }
void setValue(int v) {
if (v < 0 || v > 150) {
throw invalid_argument("Age must be between 0 and 150");
}
value = v;
}
int getValue() const { return value; }
};
Invariant Validation
class Fraction {
private:
int numerator;
int denominator;
// Maintain invariant: denominator > 0, GCD = 1
void normalize() {
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
int g = gcd(abs(numerator), denominator);
numerator /= g;
denominator /= g;
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public:
Fraction(int num, int den) : numerator(num), denominator(den) {
if (den == 0) {
throw invalid_argument("Denominator cannot be zero");
}
normalize();
}
int getNumerator() const { return numerator; }
int getDenominator() const { return denominator; }
};
Friend Functions and Classes
Friends can access private members.
Friend Function
class Box {
private:
double length, width, height;
public:
Box(double l, double w, double h) : length(l), width(w), height(h) {}
// Declare friend function
friend double calculateVolume(const Box& b);
};
// Friend function can access private members
double calculateVolume(const Box& b) {
return b.length * b.width * b.height; // OK: friend access
}
Friend Class
class Engine {
private:
int horsepower;
bool running;
friend class Car; // Car can access Engine's privates
public:
Engine(int hp) : horsepower(hp), running(false) {}
bool isRunning() const { return running; }
};
class Car {
private:
Engine engine;
public:
Car(int hp) : engine(hp) {}
void start() {
engine.running = true; // OK: Car is friend of Engine
}
void stop() {
engine.running = false;
}
};
Friend Method
class A; // Forward declaration
class B {
public:
void accessA(A& a); // Will access A's private members
};
class A {
private:
int secret;
friend void B::accessA(A& a); // Only this method is friend
public:
A(int s) : secret(s) {}
};
void B::accessA(A& a) {
cout << a.secret << endl; // OK: this method is friend
}
When to Use Friends
Appropriate uses:
- •Operator overloading (e.g.,
operator<<) - •Factory classes
- •Closely related classes (like containers and iterators)
Avoid:
- •Breaking encapsulation for convenience
- •Using when getters/setters would work
Struct vs Class
The Only Difference
struct MyStruct {
int x; // public by default
};
class MyClass {
int x; // private by default
};
Convention
// Use struct for plain data (POD - Plain Old Data)
struct Point {
double x, y;
};
struct Color {
uint8_t r, g, b, a;
};
// Use class for objects with behavior/invariants
class BankAccount {
private:
double balance;
public:
void deposit(double amount);
bool withdraw(double amount);
};
Modern C++ Preference
// C++11 and later: often use struct with public members
// and class with encapsulation
struct Config { // Just data, no invariants
string name;
int value;
};
class Connection { // Has invariants and behavior
private:
Socket socket;
bool connected;
public:
bool connect(const string& host);
void disconnect();
};
Best Practices
✅ Do
// 1. Make data members private
class Good {
private:
int data;
public:
int getData() const { return data; }
};
// 2. Validate in setters
void setAge(int a) {
if (a >= 0 && a <= 150) {
age = a;
}
}
// 3. Use const for getters
string getName() const { return name; }
// 4. Return by const reference for large objects
const vector<int>& getItems() const { return items; }
// 5. Minimize public interface
class Minimal {
public:
void doTask(); // Only expose what users need
private:
void step1(); // Hide implementation
void step2();
void step3();
};
// 6. Use initialization over assignment
class Efficient {
string name;
public:
Efficient(const string& n) : name(n) {} // Direct init
};
❌ Don't
// 1. Don't expose implementation details
class Bad {
public:
int internalBuffer[100]; // Should be private
};
// 2. Don't return non-const references to private data unnecessarily
int& getAge() { return age; } // Breaks encapsulation
// 3. Don't use friends excessively
class Overly {
friend class A;
friend class B;
friend class C; // Too many friends!
};
// 4. Don't mix responsibilities
class DoesEverything {
void saveToFile();
void drawOnScreen();
void sendOverNetwork();
void validateInput();
// ... too many responsibilities
};
// 5. Don't duplicate validation logic
void setX(int x) { /* validation */ }
void setY(int y) { /* same validation */ } // DRY violation
Encapsulation Guidelines
- •Minimize accessibility - Make everything as private as possible
- •Prefer immutability - Make objects immutable when practical
- •Validate input - Never trust external data
- •Hide implementation - Only expose what users need
- •Program to interfaces - Depend on abstractions, not implementations
Quick Reference
class WellEncapsulated {
private: // Implementation details
int value;
string name;
mutable int accessCount; // Can change in const methods
public: // Public interface
// Constructor with validation
WellEncapsulated(int v, const string& n);
// Getters (const)
int getValue() const;
const string& getName() const;
// Setters (with validation)
void setValue(int v);
void setName(const string& n);
// Operations
void doSomething();
protected: // For derived classes
void helperMethod();
private: // Private helpers
void internalValidation();
bool isValid() const;
friend ostream& operator<<(ostream&, const WellEncapsulated&);
};
Compile & Run
g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples