Home C++ encapsulation in C++ tutorial with code examples

encapsulation in C++ tutorial with code examples

In C++, encapsulation is an object-oriented programming principle that combines data (attributes) and functions (methods) into a single unit called a class.

Encapsulation helps control access to the data by making it accessible only through well-defined interfaces (public methods) while keeping the data itself hidden (private).

This improves data security and modularity.

Key Concepts of Encapsulation

  • Private Access Specifier: Data is often made private so it cannot be accessed directly from outside the class.
  • Public Access Specifier: Functions that access or modify private data are made public, providing controlled access.
  • Getters and Setters: Special public methods (getters and setters) are used to read and modify private data.

Benefits of Encapsulation

  1. Data Protection: Protects internal data from unintended access or modification.
  2. Modularity: Changes in data representation don’t affect code outside the class.
  3. Code Maintenance: Reduces dependency on internal implementation, simplifying changes and maintenance.

Let’s explore encapsulation with examples.

1. Basic Encapsulation with Getters and Setters

Encapsulating private data using public getter and setter methods.

#include <iostream>
using namespace std;

class Car {
private:
    string make;
    string model;
    int year;

public:
    // Setter methods
    void setMake(const string& m) {
        make = m;
    }

    void setModel(const string& mo) {
        model = mo;
    }

    void setYear(int y) {
        year = y;
    }

    // Getter methods
    string getMake() const {
        return make;
    }

    string getModel() const {
        return model;
    }

    int getYear() const {
        return year;
    }
};

int main() {
    Car car;
    car.setMake("Toyota");
    car.setModel("Corolla");
    car.setYear(2021);

    cout << "Car Make: " << car.getMake() << endl;
    cout << "Car Model: " << car.getModel() << endl;
    cout << "Car Year: " << car.getYear() << endl;

    return 0;
}

Explanation:

  • The make, model, and year attributes are private and accessible only through public getter and setter methods.
  • Getters (getMake, getModel, getYear) allow reading data, and setters (setMake, setModel, setYear) allow modifying data.

Output:

Car Make: Toyota
Car Model: Corolla
Car Year: 2021

2. Validating Data in Setters

Encapsulation allows adding validation logic within setters to ensure data consistency.

#include <iostream>
using namespace std;

class BankAccount {
private:
    double balance;

public:
    BankAccount() : balance(0.0) {}

    // Setter with validation
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            cout << "Deposited: $" << amount << endl;
        } else {
            cout << "Deposit amount must be positive." << endl;
        }
    }

    void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            cout << "Withdrew: $" << amount << endl;
        } else {
            cout << "Invalid withdrawal amount." << endl;
        }
    }

    // Getter
    double getBalance() const {
        return balance;
    }
};

int main() {
    BankAccount account;
    account.deposit(100.0);
    account.withdraw(50.0);
    account.withdraw(100.0);

    cout << "Current Balance: $" << account.getBalance() << endl;

    return 0;
}

Explanation:

  • The deposit and withdraw methods contain validation logic to ensure only valid transactions are processed.
  • This prevents invalid operations and enforces data consistency.

Output:

Deposited: $100
Withdrew: $50
Invalid withdrawal amount.
Current Balance: $50

3. Encapsulation with Read-Only Properties

Some data can be set only once and then accessed but not modified. This is achieved by providing only a getter method without a setter.

#include <iostream>
using namespace std;

class Student {
private:
    string name;
    int age;

public:
    // Constructor for one-time setting
    Student(const string& n, int a) : name(n), age(a) {}

    // Getter methods (no setters, making data read-only)
    string getName() const {
        return name;
    }

    int getAge() const {
        return age;
    }
};

int main() {
    Student student("Alice", 20);
    cout << "Name: " << student.getName() << endl;
    cout << "Age: " << student.getAge() << endl;

    // No setter functions available to modify name and age
    return 0;
}

Explanation:

  • name and age can only be set through the constructor.
  • The lack of setters makes name and age read-only once initialized.

Output:

Name: Alice
Age: 20

4. Encapsulation with Private Helper Methods

Encapsulation also allows you to add private helper methods within a class to be used internally, hiding complex logic from the outside world.

#include <iostream>
using namespace std;

class PasswordManager {
private:
    string password;

    // Private helper function for password validation
    bool isValid(const string& pass) {
        return pass.length() >= 8;
    }

public:
    // Setter method with validation using private helper
    void setPassword(const string& pass) {
        if (isValid(pass)) {
            password = pass;
            cout << "Password set successfully." << endl;
        } else {
            cout << "Password must be at least 8 characters long." << endl;
        }
    }

    // Getter
    string getPassword() const {
        return password;
    }
};

int main() {
    PasswordManager pm;
    pm.setPassword("pass");       // Fails validation
    pm.setPassword("securePass"); // Passes validation

    return 0;
}

Explanation:

  • isValid is a private helper function used only within the class to validate the password.
  • This hides the validation logic from the outside, encapsulating the internal workings.

Output:

Password must be at least 8 characters long.
Password set successfully.

5. Encapsulation for Default Values

Encapsulation allows setting default values for private members, simplifying object creation and maintenance.

#include <iostream>
using namespace std;

class Rectangle {
private:
    int length;
    int width;

public:
    // Constructor with default values
    Rectangle(int l = 1, int w = 1) : length(l), width(w) {}

    // Setters
    void setLength(int l) {
        if (l > 0) length = l;
    }

    void setWidth(int w) {
        if (w > 0) width = w;
    }

    // Getters
    int getLength() const {
        return length;
    }

    int getWidth() const {
        return width;
    }

    int area() const {
        return length * width;
    }
};

int main() {
    Rectangle rect;          // Uses default values (1, 1)
    Rectangle rect2(5, 3);   // Custom values (5, 3)

    cout << "Default Rectangle Area: " << rect.area() << endl;
    cout << "Custom Rectangle Area: " << rect2.area() << endl;

    return 0;
}

Explanation:

  • The constructor provides default values for length and width.
  • Getters and setters provide controlled access, allowing changes while ensuring values are positive.

Output:

Default Rectangle Area: 1
Custom Rectangle Area: 15

6. Encapsulation in a Class with Complex Data Types

Encapsulation can be applied to classes containing other complex data types, encapsulating multiple related attributes.

#include <iostream>
using namespace std;

class Address {
private:
    string city;
    string state;
    string zip;

public:
    // Constructor
    Address(const string& c, const string& s, const string& z) : city(c), state(s), zip(z) {}

    // Getters
    string getCity() const {
        return city;
    }

    string getState() const {
        return state;
    }

    string getZip() const {
        return zip;
    }
};

class Employee {
private:
    string name;
    Address address; // Composition

public:
    // Constructor
    Employee(const string& n, const Address& addr) : name(n), address(addr) {}

    // Getters
    string getName() const {
        return name;
    }

    Address getAddress() const {
        return address;
    }
};

int main() {
    Address addr("New York", "NY", "10001");
    Employee emp("John Doe", addr);

    cout << "Employee Name: " << emp.getName() << endl;
    cout << "City: " << emp.getAddress().getCity() << ", State: " << emp.getAddress().getState()
         << ", Zip: " << emp.getAddress().getZip() << endl;

    return 0;
}

Explanation:

  • Employee has a member of type Address.
  • Encapsulation allows controlled access to nested attributes through Employee’s interface.

Output

:

Employee Name: John Doe
City: New York, State: NY, Zip: 10001

7. Encapsulation with Constant Members

Encapsulation can be used to create members that are read-only after initialization by using constant data members.

#include <iostream>
using namespace std;

class Product {
private:
    const int id;
    string name;

public:
    // Constructor to initialize id
    Product(int p_id, const string& n) : id(p_id), name(n) {}

    // Getter for id (no setter since it's read-only)
    int getId() const {
        return id;
    }

    // Getter and Setter for name
    string getName() const {
        return name;
    }

    void setName(const string& n) {
        name = n;
    }
};

int main() {
    Product product(101, "Laptop");

    cout << "Product ID: " << product.getId() << endl;
    cout << "Product Name: " << product.getName() << endl;

    // product.getId() is read-only and cannot be modified
    product.setName("Tablet"); // Only name can be changed
    cout << "Updated Product Name: " << product.getName() << endl;

    return 0;
}

Explanation:

  • id is a constant member and can only be set at initialization.
  • The getter getId provides read-only access to id, while setName can modify name.

Output:

Product ID: 101
Product Name: Laptop
Updated Product Name: Tablet

Summary Table of Encapsulation Concepts

Encapsulation Concept Description Example
Basic Getters and Setters Encapsulate private data with controlled access setMake, getModel
Validation in Setters Add validation to ensure data consistency deposit, withdraw
Read-Only Properties Expose data only through getters, no setters getName, getAge
Private Helper Methods Hide complex logic inside private helper functions isValid
Default Values Set default values with constructors Rectangle(int l=1, int w=1)
Composition of Classes Encapsulate related data types within a class Employee with Address
Constant Members Provide read-only access to constants const int id

Complete Example

This example combines encapsulation concepts like getters, setters, validation, default values, and nested classes.

#include <iostream>
using namespace std;

class Address {
private:
    string city;
    string state;

public:
    Address(const string& c, const string& s) : city(c), state(s) {}

    string getCity() const { return city; }
    string getState() const { return state; }
};

class Student {
private:
    const int id;
    string name;
    Address address;

public:
    // Constructor with initialization list
    Student(int i, const string& n, const Address& addr) : id(i), name(n), address(addr) {}

    // Getters
    int getId() const { return id; }
    string getName() const { return name; }
    Address getAddress() const { return address; }

    // Setter with validation
    void setName(const string& n) {
        if (!n.empty()) {
            name = n;
        } else {
            cout << "Name cannot be empty." << endl;
        }
    }
};

int main() {
    Address addr("Los Angeles", "CA");
    Student student(1, "Alice", addr);

    cout << "Student ID: " << student.getId() << endl;
    cout << "Student Name: " << student.getName() << endl;
    cout << "Student Address: " << student.getAddress().getCity() << ", " << student.getAddress().getState() << endl;

    student.setName("Bob");
    cout << "Updated Name: " << student.getName() << endl;

    return 0;
}

Sample Output:

Student ID: 1
Student Name: Alice
Student Address: Los Angeles, CA
Updated Name: Bob

Encapsulation in C++ allows for flexible, controlled data access while protecting internal data.

You may also like