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
- Data Protection: Protects internal data from unintended access or modification.
- Modularity: Changes in data representation don’t affect code outside the class.
- 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.