C++ Encapsulation
Overview
Encapsulation is one of the basic principles of object-oriented programming. It binds data (attributes) and methods that operate on data (behaviors) together, hiding the internal implementation details of objects. Encapsulation protects data integrity through access control mechanisms and provides clean external interfaces.
🔒 Basic Encapsulation Concepts
Data Hiding and Access Control
cpp
#include <iostream>
#include <string>
#include <stdexcept>
class BankAccount {
private:
// Private data members: External direct access is not allowed
std::string accountNumber_;
std::string ownerName_;
double balance_;
std::string pin_;
bool locked_;
// Private helper methods
bool validatePin(const std::string& inputPin) const {
return pin_ == inputPin;
}
void logTransaction(const std::string& operation, double amount) const {
std::cout << "[Log] " << accountNumber_ << ": " << operation
<< " " << amount << ", Balance: " << balance_ << std::endl;
}
public:
// Constructor: Ensure valid object creation state
BankAccount(const std::string& accountNumber, const std::string& ownerName,
const std::string& pin, double initialBalance = 0.0)
: accountNumber_(accountNumber), ownerName_(ownerName),
pin_(pin), balance_(initialBalance), locked_(false) {
if (initialBalance < 0) {
throw std::invalid_argument("Initial balance cannot be negative");
}
std::cout << "Account created successfully: " << accountNumber_ << std::endl;
}
// Public interface: Control access to data
bool deposit(double amount, const std::string& pin) {
if (!validatePin(pin)) {
std::cout << "PIN code error" << std::endl;
return false;
}
if (locked_) {
std::cout << "Account is locked" << std::endl;
return false;
}
if (amount <= 0) {
std::cout << "Deposit amount must be greater than 0" << std::endl;
return false;
}
balance_ += amount;
logTransaction("Deposit", amount);
return true;
}
bool withdraw(double amount, const std::string& pin) {
if (!validatePin(pin)) {
std::cout << "PIN code error" << std::endl;
return false;
}
if (locked_) {
std::cout << "Account is locked" << std::endl;
return false;
}
if (amount <= 0) {
std::cout << "Withdrawal amount must be greater than 0" << std::endl;
return false;
}
if (amount > balance_) {
std::cout << "Insufficient balance" << std::endl;
return false;
}
balance_ -= amount;
logTransaction("Withdrawal", amount);
return true;
}
// Read-only accessors
double getBalance(const std::string& pin) const {
if (!validatePin(pin)) {
std::cout << "PIN code error" << std::endl;
return -1; // Error identifier
}
return balance_;
}
std::string getAccountNumber() const {
return accountNumber_; // Account number can be public
}
std::string getOwnerName() const {
return ownerName_;
}
// Security operations
void lockAccount() {
locked_ = true;
std::cout << "Account locked" << std::endl;
}
void unlockAccount(const std::string& pin) {
if (validatePin(pin)) {
locked_ = false;
std::cout << "Account unlocked" << std::endl;
} else {
std::cout << "PIN code error, cannot unlock" << std::endl;
}
}
bool isLocked() const {
return locked_;
}
};
int main() {
std::cout << "=== Basic Encapsulation ===" << std::endl;
try {
BankAccount account("123456789", "Zhang San", "1234", 1000.0);
// Safe operations through public interface
std::cout << "Account holder: " << account.getOwnerName() << std::endl;
std::cout << "Balance: " << account.getBalance("1234") << std::endl;
// Normal operations
account.deposit(500, "1234");
account.withdraw(200, "1234");
// Error operations are prevented
account.withdraw(2000, "1234"); // Insufficient balance
account.deposit(100, "5678"); // PIN error
// Security mechanisms
account.lockAccount();
account.deposit(100, "1234"); // Account locked
account.unlockAccount("1234");
// Cannot directly access private members
// std::cout << account.balance_; // Compilation error
// account.pin_ = "0000"; // Compilation error
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}🎯 Getter and Setter Methods
Accessor and Mutator Design
cpp
#include <iostream>
#include <string>
#include <algorithm>
class Employee {
private:
std::string name_;
int age_;
double salary_;
std::string department_;
int employeeId_;
static int nextId_;
public:
Employee(const std::string& name, int age, double salary, const std::string& department)
: name_(name), age_(age), salary_(salary), department_(department), employeeId_(++nextId_) {
validateEmployee();
}
// Getter methods: Provide read-only access
std::string getName() const { return name_; }
int getAge() const { return age_; }
double getSalary() const { return salary_; }
std::string getDepartment() const { return department_; }
int getEmployeeId() const { return employeeId_; }
// Setter methods: Provide controlled modification
void setName(const std::string& name) {
if (name.empty()) {
throw std::invalid_argument("Name cannot be empty");
}
name_ = name;
std::cout << "Name updated to: " << name_ << std::endl;
}
void setAge(int age) {
if (age < 18 || age > 65) {
throw std::invalid_argument("Age must be between 18-65");
}
age_ = age;
std::cout << "Age updated to: " << age_ << std::endl;
}
void setSalary(double salary) {
if (salary < 0) {
throw std::invalid_argument("Salary cannot be negative");
}
double oldSalary = salary_;
salary_ = salary;
std::cout << "Salary updated from " << oldSalary << " to " << salary_ << std::endl;
// Log salary changes
if (salary > oldSalary) {
std::cout << "Congratulations! Salary increased by " << (salary - oldSalary) << std::endl;
}
}
void setDepartment(const std::string& department) {
if (department.empty()) {
throw std::invalid_argument("Department cannot be empty");
}
std::string oldDept = department_;
department_ = department;
std::cout << "Department moved from " << oldDept << " to " << department_ << std::endl;
}
// Read-only property: Employee ID cannot be modified
// Do not provide setEmployeeId method
};
// Computed properties
double getAnnualSalary() const {
return salary_ * 12;
}
std::string getDisplayName() const {
return name_ + " (#" + std::to_string(employeeId_) + ")";
}
void displayInfo() const {
std::cout << "Employee information:" << std::endl;
std::cout << " ID: " << employeeId_ << std::endl;
std::cout << " Name: " << name_ << std::endl;
std::cout << " Age: " << age_ << std::endl;
std::cout << " Department: " << department_ << std::endl;
std::cout << " Monthly Salary: " << salary_ << std::endl;
std::cout << " Annual Salary: " << getAnnualSalary() << std::endl;
}
private:
void validateEmployee() {
if (name_.empty()) {
throw std::invalid_argument("Name cannot be empty");
}
if (age_ < 18 || age_ > 65) {
throw std::invalid_argument("Age must be between 18-65");
}
if (salary_ < 0) {
throw std::invalid_argument("Salary cannot be negative");
}
if (department_.empty()) {
throw std::invalid_argument("Department cannot be empty");
}
}
};
int Employee::nextId_ = 0;
int main() {
std::cout << "=== Getter/Setter Example ===" << std::endl;
try {
Employee emp("Li Si", 28, 8000, "Technology Department");
emp.displayInfo();
std::cout << "\n--- Update Employee Information ---" << std::endl;
emp.setAge(30);
emp.setSalary(10000);
emp.setDepartment("Product Department");
std::cout << "\n--- Computed Properties ---" << std::endl;
std::cout << "Display Name: " << emp.getDisplayName() << std::endl;
std::cout << "Annual Salary: " << emp.getAnnualSalary() << std::endl;
// Try invalid operations
std::cout << "\n--- Validation Error Handling ---" << std::endl;
try {
emp.setAge(70); // Age out of range
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
try {
emp.setSalary(-1000); // Negative salary
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
} catch (const std::exception& e) {
std::cout << "Employee creation failed: " << e.what() << std::endl;
}
return 0;
}🏛️ Class Cohesive Design
High Cohesion Class Design
cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// High cohesion: All members revolve around "student grades" core concept
class StudentGrades {
private:
std::string studentName_;
std::string studentId_;
std::vector<double> grades_;
// Private helper methods: Support core functionality
bool isValidGrade(double grade) const {
return grade >= 0.0 && grade <= 100.0;
}
void sortGrades() {
std::sort(grades_.begin(), grades_.end(), std::greater<double>());
}
public:
StudentGrades(const std::string& name, const std::string& id)
: studentName_(name), studentId_(id) {}
// Core functionality: Grade management
bool addGrade(double grade) {
if (!isValidGrade(grade)) {
std::cout << "Invalid grade: " << grade << " (must be between 0-100)" << std::endl;
return false;
}
grades_.push_back(grade);
std::cout << "Added grade: " << grade << std::endl;
return true;
}
bool removeLowestGrade() {
if (grades_.empty()) {
std::cout << "No grades to remove" << std::endl;
return false;
}
auto min_it = std::min_element(grades_.begin(), grades_.end());
double removed = *min_it;
grades_.erase(min_it);
std::cout << "Removed lowest grade: " << removed << std::endl;
return true;
}
// Core calculation functions
double getAverageGrade() const {
if (grades_.empty()) return 0.0;
double sum = 0.0;
for (double grade : grades_) {
sum += grade;
}
return sum / grades_.size();
}
double getHighestGrade() const {
if (grades_.empty()) return 0.0;
return *std::max_element(grades_.begin(), grades_.end());
}
double getLowestGrade() const {
if (grades_.empty()) return 0.0;
return *std::min_element(grades_.begin(), grades_.end());
}
char getLetterGrade() const {
double avg = getAverageGrade();
if (avg >= 90) return 'A';
if (avg >= 80) return 'B';
if (avg >= 70) return 'C';
if (avg >= 60) return 'D';
return 'F';
}
// Accessors
std::string getStudentName() const { return studentName_; }
std::string getStudentId() const { return studentId_; }
size_t getGradeCount() const { return grades_.size(); }
// Display functionality
void displayGrades() const {
std::cout << "\nStudent: " << studentName_ << " (ID: " << studentId_ << ")" << std::endl;
std::cout << "Grade list: ";
if (grades_.empty()) {
std::cout << "No grades" << std::endl;
return;
}
for (size_t i = 0; i < grades_.size(); ++i) {
std::cout << grades_[i];
if (i < grades_.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
std::cout << "Average: " << getAverageGrade() << std::endl;
std::cout << "Highest: " << getHighestGrade() << std::endl;
std::cout << "Lowest: " << getLowestGrade() << std::endl;
std::cout << "Grade: " << getLetterGrade() << std::endl;
}
void displaySortedGrades() const {
std::cout << "\nSorted grades (highest to lowest): ";
auto sorted_grades = grades_;
std::sort(sorted_grades.begin(), sorted_grades.end(), std::greater<double>());
for (size_t i = 0; i < sorted_grades.size(); ++i) {
std::cout << sorted_grades[i];
if (i < sorted_grades.size() - 1) std::cout << " > ";
}
std::cout << std::endl;
}
};
// Comparison: Low cohesion design (not recommended)
class BadStudent {
public:
std::string name;
std::vector<double> grades;
std::string address; // Unrelated to grades
std::string phoneNumber; // Unrelated to grades
void addGrade(double grade) { grades.push_back(grade); }
void sendEmail() {} // Unrelated to student grade management
void calculateTax() {} // Completely unrelated functionality
void manageLibraryBooks() {} // Confused responsibilities
};
int main() {
std::cout << "=== Class Cohesion Design ===" << std::endl;
StudentGrades student("Wang Xiaoming", "2023001");
// Add grades
student.addGrade(85.5);
student.addGrade(92.0);
student.addGrade(78.5);
student.addGrade(88.0);
// Try adding invalid grades
student.addGrade(105.0); // Out of range
student.addGrade(-10.0); // Negative number
// Display grade information
student.displayGrades();
student.displaySortedGrades();
// Delete lowest grade
std::cout << "\n--- Delete Lowest Grade ---" << std::endl;
student.removeLowestGrade();
student.displayGrades();
std::cout << "\n=== Design Principles ===" << std::endl;
std::cout << "✓ High cohesion: All members revolve around core responsibility" << std::endl;
std::cout << "✓ Single responsibility: Only responsible for student grade management" << std::endl;
std::cout << "✓ Data validation: Ensure data integrity" << std::endl;
std::cout << "✓ Clear interface: Provide explicit operation methods" << std::endl;
return 0;
}🛡️ Invariants and Data Integrity
Class Invariant Maintenance
cpp
#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>
class Rectangle {
private:
double width_;
double height_;
// Class invariants: width and height must be positive
void maintainInvariant() const {
if (width_ <= 0 || height_ <= 0) {
throw std::logic_error("Rectangle width and height must be positive");
}
}
public:
Rectangle(double width, double height) : width_(width), height_(height) {
if (width <= 0 || height <= 0) {
throw std::invalid_argument("Rectangle width and height must be positive");
}
maintainInvariant(); // Ensure object satisfies invariants when created
}
// Modify operations must maintain invariants
void setWidth(double width) {
if (width <= 0) {
throw std::invalid_argument("Width must be positive");
}
width_ = width;
maintainInvariant();
}
void setHeight(double height) {
if (height <= 0) {
throw std::invalid_argument("Height must be positive");
}
height_ = height;
maintainInvariant();
}
void scale(double factor) {
if (factor <= 0) {
throw std::invalid_argument("Scale factor must be positive");
}
width_ *= factor;
height_ *= factor;
maintainInvariant();
}
// Accessors don't break invariants
double getWidth() const { return width_; }
double getHeight() const { return height_; }
double getArea() const { return width_ * height_; }
double getPerimeter() const { return 2 * (width_ + height_); }
void display() const {
std::cout << "Rectangle: " << width_ << " x " << height_
<< ", Area: " << getArea()
<< ", Perimeter: " << getPerimeter() << std::endl;
}
};
// Complex invariants example: Sorted set
class SortedSet {
private:
std::vector<int> data_;
// Invariant: Data must remain sorted and no duplicates
void maintainInvariant() const {
if (!std::is_sorted(data_.begin(), data_.end())) {
throw std::logic_error("Data must maintain sorted order");
}
if (std::adjacent_find(data_.begin(), data_.end()) != data_.end()) {
throw std::logic_error("Data cannot have duplicate elements");
}
}
public:
SortedSet() = default;
bool insert(int value) {
// Find insertion position
auto it = std::lower_bound(data_.begin(), data_.end(), value);
// If element already exists, don't insert
if (it != data_.end() && *it == value) {
return false;
}
// Insert at correct position
data_.insert(it, value);
maintainInvariant();
return true;
}
bool remove(int value) {
auto it = std::lower_bound(data_.begin(), data_.end(), value);
if (it != data_.end() && *it == value) {
data_.erase(it);
maintainInvariant();
return true;
}
return false;
}
bool contains(int value) const {
auto it = std::lower_bound(data_.begin(), data_.end(), value);
return it != data_.end() && *it == value;
}
size_t size() const { return data_.size(); }
bool empty() const { return data_.empty(); }
void display() const {
std::cout << "Sorted set: { ";
for (size_t i = 0; i < data_.size(); ++i) {
std::cout << data_[i];
if (i < data_.size() - 1) std::cout << ", ";
}
std::cout << " }" << std::endl;
}
// Provide controlled iterator access
std::vector<int>::const_iterator begin() const { return data_.begin(); }
std::vector<int>::const_iterator end() const { return data_.end(); }
// Don't provide non-const iterators to prevent external invariants break
};
int main() {
std::cout << "=== Invariants and Data Integrity ===" << std::endl;
// Simple invariants test
try {
Rectangle rect(5.0, 3.0);
rect.display();
rect.setWidth(8.0);
rect.display();
rect.scale(1.5);
rect.display();
// Try breaking invariants
try {
rect.setWidth(-2.0); // Should fail
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
} catch (const std::exception& e) {
std::cout << "Rectangle error: " << e.what() << std::endl;
}
// Sorted set invariants test
std::cout << "\n--- Sorted Set Invariants Test ---" << std::endl;
SortedSet set;
// Insert elements
std::vector<int> values = {5, 2, 8, 2, 1, 9, 5};
for (int val : values) {
bool inserted = set.insert(val);
std::cout << "Insert " << val << ": " << (inserted ? "success" : "already exists") << std::endl;
}
set.display();
// Find element
std::cout << "Contains 5: " << (set.contains(5) ? "Yes" : "No") << std::endl;
std::cout << "Contains 10: " << (set.contains(10) ? "Yes" : "No") << std::endl;
// Delete element
set.remove(2);
std::cout << "After removing 2: ";
set.display();
return 0;
}Summary
Encapsulation is a fundamental principle of object-oriented programming, ensuring code safety and maintainability through data hiding and interface control:
Encapsulation Elements
- Data Hiding: Private member variables
- Interface Control: Public methods provide access
- Data Validation: Ensure data integrity
- Invariant Maintenance: Maintain consistent object state
Access Control Levels
| Level | Class Internal | Derived Classes | External Code | Usage |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | Internal implementation |
| protected | ✓ | ✓ | ✗ | Inheritance interfaces |
| public | ✓ | ✓ | ✓ | External interface |
Design Principles
- Least Privilege Principle: Grant minimal necessary access permissions
- Interface Stability: Change public interfaces cautiously
- Data Validation: Validate input in setters
- Invariant Maintenance: Ensure consistent object state
- High Cohesion: Class members revolve around core responsibility
Best Practices
- Prioritize private, use protected when necessary
- Provide clear getter/setter interfaces
- Establish invariants in constructors
- Control state changes through methods
- Avoid exposing internal data structures
Good encapsulation design makes code more secure, maintainable, and reusable, forming an important foundation for building robust software systems.