Docs

README

Encapsulation in C++

Table of Contents

  1. What is Encapsulation
  2. Access Modifiers
  3. Getters and Setters
  4. Data Validation
  5. Friend Functions and Classes
  6. Struct vs Class
  7. 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

BenefitDescription
Data ProtectionPrevent invalid states
FlexibilityChange internals without breaking external code
MaintainabilityEasier to debug and modify
AbstractionUsers 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

AccessorSame ClassDerived ClassOutside
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

  1. Minimize accessibility - Make everything as private as possible
  2. Prefer immutability - Make objects immutable when practical
  3. Validate input - Never trust external data
  4. Hide implementation - Only expose what users need
  5. 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
README - C++ Tutorial | DeepML