C++ Object-Oriented Programming: Complete Guide
Master C++ OOP fundamentals: classes, objects, inheritance, polymorphism, encapsulation, and abstraction with real-world examples and best practices.
Encapsulation
Data hiding and protection
Inheritance
Code reusability and hierarchy
Polymorphism
Many forms, single interface
Abstraction
Hide complexity, show essentials
Introduction to Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around data, or objects, rather than functions and logic. C++ is a multi-paradigm language that fully supports OOP.
Real-World Analogy
Think of a Car as a class. Each individual car (Toyota Camry, Honda Civic) is an object. All cars inherit basic properties from the Vehicle class. Different cars (electric, gasoline) implement the same interface (start(), stop()) differently - that's polymorphism!
Why Use OOP?
- Modularity for easier maintenance
- Code reusability through inheritance
- Data hiding and security
- Flexibility through polymorphism
- Better problem-solving approach
- Easier collaboration in teams
OOP vs Procedural Programming
- Procedural: Focus on functions
- OOP: Focus on data and objects
- Procedural: Data is global or passed
- OOP: Data is encapsulated in objects
- Procedural: Harder to maintain
- OOP: Easier to scale and maintain
C++ OOP Concepts Overview
The following table explains the fundamental concepts of Object-Oriented Programming in C++:
| Concept | Definition | C++ Implementation | Real-World Example |
|---|---|---|---|
| Class | Blueprint or template for creating objects | class ClassName { }; | Car design blueprint |
| Object | Instance of a class with actual data | ClassName obj; | Actual car on the road |
| Encapsulation | Bundling data and methods together, hiding implementation | private: and public: | Capsule containing medicine |
| Inheritance | Deriving new classes from existing ones | class Child : public Parent | Child inherits traits from parents |
| Polymorphism | One interface, multiple implementations | virtual functions, overriding | Person can be Student, Teacher, etc. |
| Abstraction | Hiding complex reality while exposing essentials | Abstract classes, interfaces | Car dashboard (hides engine complexity) |
| Constructor | Special method called when object is created | ClassName() { } | Car assembly process |
| Destructor | Special method called when object is destroyed | ~ClassName() { } | Car recycling process |
1. Classes and Objects: The Foundation
A class is a user-defined data type that holds both data (attributes) and functions (methods). An object is an instance of a class.
Class Definition Syntax
class ClassName {
private:
// Private members (accessible only within class)
dataType privateVariable;
protected:
// Protected members (accessible within class and derived classes)
dataType protectedVariable;
public:
// Public members (accessible from anywhere)
dataType publicVariable;
// Constructor
ClassName(parameters) {
// Initialization code
}
// Member functions (methods)
returnType methodName(parameters) {
// Method implementation
}
// Destructor
~ClassName() {
// Cleanup code
}
};
#include <iostream>
#include <string>
using namespace std;
// ======================
// CLASS DECLARATION
// ======================
class BankAccount {
private:
// Private data members (encapsulation)
string accountNumber;
string accountHolder;
double balance;
public:
// ======================
// CONSTRUCTORS
// ======================
// Default constructor
BankAccount() {
accountNumber = "000000";
accountHolder = "Unknown";
balance = 0.0;
cout << "Default constructor called" << endl;
}
// Parameterized constructor
BankAccount(string accNum, string holder, double initialBalance) {
accountNumber = accNum;
accountHolder = holder;
if (initialBalance >= 0) {
balance = initialBalance;
} else {
balance = 0.0;
cout << "Warning: Negative balance set to 0" << endl;
}
cout << "Parameterized constructor called for " << holder << endl;
}
// Copy constructor
BankAccount(const BankAccount& other) {
accountNumber = other.accountNumber + "-COPY";
accountHolder = other.accountHolder + " (Copy)";
balance = other.balance;
cout << "Copy constructor called" << endl;
}
// ======================
// DESTRUCTOR
// ======================
~BankAccount() {
cout << "Destructor called for " << accountHolder << endl;
}
// ======================
// PUBLIC MEMBER FUNCTIONS (Interface)
// ======================
// Getter methods (accessors)
string getAccountNumber() const {
return accountNumber;
}
string getAccountHolder() const {
return accountHolder;
}
double getBalance() const {
return balance;
}
// Setter methods (mutators)
void setAccountHolder(string newHolder) {
if (!newHolder.empty()) {
accountHolder = newHolder;
}
}
// Business logic methods
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited: $" << amount << endl;
cout << "New balance: $" << balance << endl;
} else {
cout << "Invalid deposit amount!" << endl;
}
}
bool withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrawn: $" << amount << endl;
cout << "New balance: $" << balance << endl;
return true;
} else {
cout << "Insufficient funds or invalid amount!" << endl;
return false;
}
}
void displayAccountInfo() const {
cout << "\n=== Account Information ===" << endl;
cout << "Account Number: " << accountNumber << endl;
cout << "Account Holder: " << accountHolder << endl;
cout << "Balance: $" << balance << endl;
cout << "===========================\n" << endl;
}
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== BANK ACCOUNT SYSTEM DEMONSTRATION ===" << endl << endl;
// ======================
// CREATING OBJECTS
// ======================
// Method 1: Default constructor
cout << "1. Creating account with default constructor:" << endl;
BankAccount account1; // Default constructor called
account1.displayAccountInfo();
// Method 2: Parameterized constructor
cout << "2. Creating account with parameterized constructor:" << endl;
BankAccount account2("123456", "John Doe", 1000.00);
account2.displayAccountInfo();
// Method 3: Copy constructor
cout << "3. Creating account using copy constructor:" << endl;
BankAccount account3 = account2; // Copy constructor called
account3.displayAccountInfo();
cout << "===========================================" << endl << endl;
// ======================
// USING OBJECT METHODS
// ======================
cout << "4. Performing transactions on John's account:" << endl;
// Deposit money
cout << "\nDepositing $500..." << endl;
account2.deposit(500.00);
// Withdraw money
cout << "\nWithdrawing $200..." << endl;
account2.withdraw(200.00);
// Try to withdraw too much
cout << "\nTrying to withdraw $2000..." << endl;
account2.withdraw(2000.00);
// Display updated info
account2.displayAccountInfo();
cout << "===========================================" << endl << endl;
// ======================
// ACCESSING MEMBERS
// ======================
cout << "5. Accessing account information using getters:" << endl;
cout << "Account Number: " << account2.getAccountNumber() << endl;
cout << "Account Holder: " << account2.getAccountHolder() << endl;
cout << "Balance: $" << account2.getBalance() << endl;
cout << "\n6. Changing account holder name:" << endl;
account2.setAccountHolder("John Smith");
cout << "New account holder: " << account2.getAccountHolder() << endl;
cout << "===========================================" << endl << endl;
// ======================
// ARRAY OF OBJECTS
// ======================
cout << "7. Creating array of BankAccount objects:" << endl;
BankAccount accounts[3] = {
BankAccount("111111", "Alice", 500.00),
BankAccount("222222", "Bob", 1500.00),
BankAccount("333333", "Charlie", 2500.00)
};
// Display all accounts
for (int i = 0; i < 3; i++) {
accounts[i].displayAccountInfo();
}
cout << "===========================================" << endl << endl;
// ======================
// POINTERS TO OBJECTS
// ======================
cout << "8. Using pointers with objects:" << endl;
// Creating object using pointer
BankAccount* ptrAccount = new BankAccount("444444", "David", 3000.00);
// Accessing members through pointer
cout << "Account holder (via pointer): " << ptrAccount->getAccountHolder() << endl;
// Calling methods through pointer
ptrAccount->deposit(1000.00);
// Don't forget to delete!
delete ptrAccount;
cout << "===========================================" << endl << endl;
// ======================
// CONST OBJECTS
// ======================
cout << "9. Working with const objects:" << endl;
const BankAccount constAccount("555555", "Eve", 4000.00);
// Can only call const member functions on const objects
cout << "Const account balance: $" << constAccount.getBalance() << endl;
constAccount.displayAccountInfo();
// This would cause compilation error:
// constAccount.deposit(100.00); // Error: deposit() is not const
cout << "===========================================" << endl << endl;
// Destructors will be called automatically when objects go out of scope
cout << "Program ending. Destructors will be called automatically..." << endl;
return 0;
}
- Use meaningful, descriptive class names
- Keep data members private (encapsulation)
- Provide public getter/setter methods if needed
- Initialize all data members in constructors
- Follow the Rule of Three/Five for resource management
- Make member functions const when they don't modify object
- Forgetting semicolon after class definition
- Making all data members public (breaks encapsulation)
- Not initializing pointers in constructors
- Memory leaks (not deleting dynamic memory)
- Shallow copying when deep copy is needed
- Not making destructor virtual in base class
2. Constructors and Destructors
Constructors initialize objects when they are created. Destructors clean up when objects are destroyed. C++ provides several types of constructors.
Default Constructor
- No parameters
- Called when:
ClassName obj; - Initializes with default values
- Compiler provides if no constructors defined
Parameterized Constructor
- Accepts parameters
- Called when:
ClassName obj(params); - Initializes with provided values
- Allows custom initialization
Copy Constructor
- Creates copy of existing object
- Called when:
ClassName obj2 = obj1; - Deep vs shallow copy
- Compiler provides default if not defined
#include <iostream>
#include <string>
#include <cstring> // For strlen, strcpy
using namespace std;
// ======================
// 1. SIMPLE CONSTRUCTOR/DESTRUCTOR
// ======================
class SimpleClass {
private:
int id;
string name;
public:
// Default constructor
SimpleClass() {
id = 0;
name = "Default";
cout << "Default constructor called" << endl;
}
// Parameterized constructor
SimpleClass(int i, string n) {
id = i;
name = n;
cout << "Parameterized constructor called for " << name << endl;
}
// Copy constructor
SimpleClass(const SimpleClass& other) {
id = other.id;
name = other.name + " (Copy)";
cout << "Copy constructor called" << endl;
}
// Destructor
~SimpleClass() {
cout << "Destructor called for " << name << endl;
}
void display() {
cout << "ID: " << id << ", Name: " << name << endl;
}
};
// ======================
// 2. DYNAMIC MEMORY MANAGEMENT
// ======================
class DynamicClass {
private:
char* buffer;
int size;
public:
// Constructor with dynamic allocation
DynamicClass(const char* str) {
size = strlen(str);
buffer = new char[size + 1];
strcpy(buffer, str);
cout << "DynamicClass created: " << buffer << endl;
}
// Copy constructor (DEEP COPY)
DynamicClass(const DynamicClass& other) {
size = other.size;
buffer = new char[size + 1];
strcpy(buffer, other.buffer);
cout << "Deep copy created: " << buffer << endl;
}
// Destructor
~DynamicClass() {
cout << "DynamicClass destroyed: " << buffer << endl;
delete[] buffer; // CRITICAL: Free allocated memory
}
void display() {
cout << "Content: " << buffer << " (Size: " << size << ")" << endl;
}
};
// ======================
// 3. CONSTRUCTOR OVERLOADING
// ======================
class Rectangle {
private:
double length;
double width;
public:
// Constructor 1: No parameters
Rectangle() {
length = 1.0;
width = 1.0;
cout << "Default rectangle created (1x1)" << endl;
}
// Constructor 2: One parameter (square)
Rectangle(double side) {
length = side;
width = side;
cout << "Square created: " << side << "x" << side << endl;
}
// Constructor 3: Two parameters
Rectangle(double l, double w) {
length = l;
width = w;
cout << "Rectangle created: " << l << "x" << w << endl;
}
// Constructor 4: Using initializer list (preferred)
Rectangle(int l, int w) : length(l), width(w) {
cout << "Rectangle created with initializer list: "
<< l << "x" << w << endl;
}
double area() {
return length * width;
}
void display() {
cout << "Length: " << length << ", Width: " << width
<< ", Area: " << area() << endl;
}
};
// ======================
// 4. DESTRUCTOR DEMONSTRATION
// ======================
class ResourceHolder {
private:
int* data;
int size;
public:
// Constructor
ResourceHolder(int s) : size(s) {
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = i * 10;
}
cout << "ResourceHolder created with " << size << " elements" << endl;
}
// DESTRUCTOR - Cleans up dynamically allocated memory
~ResourceHolder() {
cout << "ResourceHolder destroyed. Freeing " << size << " elements" << endl;
delete[] data; // Prevents memory leak
}
void display() {
cout << "Data: ";
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== CONSTRUCTORS AND DESTRUCTORS DEMONSTRATION ===" << endl << endl;
// ======================
// 1. BASIC CONSTRUCTORS
// ======================
cout << "1. Basic Constructors:" << endl;
{
cout << "\nEntering block scope..." << endl;
SimpleClass obj1; // Default constructor
SimpleClass obj2(101, "Alice"); // Parameterized constructor
SimpleClass obj3 = obj2; // Copy constructor
obj1.display();
obj2.display();
obj3.display();
cout << "Exiting block scope..." << endl;
} // Destructors called here automatically
cout << "\n===========================================" << endl << endl;
// ======================
// 2. DYNAMIC MEMORY
// ======================
cout << "2. Dynamic Memory Management:" << endl;
{
DynamicClass dyn1("Hello World");
DynamicClass dyn2 = dyn1; // Deep copy
dyn1.display();
dyn2.display();
// Note: No need to manually delete - destructor handles it!
}
cout << "\n===========================================" << endl << endl;
// ======================
// 3. CONSTRUCTOR OVERLOADING
// ======================
cout << "3. Constructor Overloading:" << endl;
Rectangle rect1; // Calls default constructor
Rectangle rect2(5.0); // Calls one-parameter constructor
Rectangle rect3(4.0, 6.0); // Calls two-parameter constructor
Rectangle rect4(3, 7); // Calls constructor with initializer list
rect1.display();
rect2.display();
rect3.display();
rect4.display();
cout << "\n===========================================" << endl << endl;
// ======================
// 4. DESTRUCTOR IMPORTANCE
// ======================
cout << "4. Destructor Importance:" << endl;
{
cout << "\nCreating ResourceHolder in inner scope..." << endl;
ResourceHolder rh(5);
rh.display();
cout << "Leaving inner scope..." << endl;
} // Destructor automatically called here
cout << "\nMemory was automatically freed by destructor!" << endl;
cout << "\n===========================================" << endl << endl;
// ======================
// 5. ARRAY OF OBJECTS
// ======================
cout << "5. Array of Objects:" << endl;
SimpleClass objects[3] = {
SimpleClass(), // Default constructor
SimpleClass(201, "Bob"), // Parameterized constructor
SimpleClass(202, "Charlie") // Parameterized constructor
};
for (int i = 0; i < 3; i++) {
objects[i].display();
}
cout << "\n===========================================" << endl << endl;
// ======================
// 6. POINTERS AND NEW/DELETE
// ======================
cout << "6. Pointers and Dynamic Objects:" << endl;
// Creating object with new
SimpleClass* ptr = new SimpleClass(301, "David");
ptr->display();
// Must delete manually when using new
delete ptr; // Calls destructor
cout << "\n===========================================" << endl << endl;
cout << "Key Takeaways:" << endl;
cout << "1. Constructors initialize objects" << endl;
cout << "2. Destructors clean up resources" << endl;
cout << "3. Always free dynamic memory in destructor" << endl;
cout << "4. Use initializer lists for efficiency" << endl;
cout << "5. Copy constructor should do deep copy for pointers" << endl;
return 0;
}
- Use initializer lists for member initialization (more efficient)
- Provide a default constructor if objects might be created without arguments
- Make copy constructor do deep copy when class has pointer members
- Follow the Rule of Three: If you need destructor, copy constructor, or copy assignment, you likely need all three
- Use delegating constructors (C++11) to avoid code duplication
- Initialize all data members in every constructor
3. Encapsulation: Data Hiding and Protection
Encapsulation is the bundling of data and methods that operate on that data within a single unit (class), and restricting direct access to some of the object's components.
Access Specifiers
class Example {
private: // Accessible ONLY within this class
int secretData;
protected: // Accessible within this class AND derived classes
int familyData;
public: // Accessible from ANYWHERE
int publicData;
// Getter method (accessor)
int getSecretData() const {
return secretData;
}
// Setter method (mutator) with validation
void setSecretData(int value) {
if (value >= 0) {
secretData = value;
}
}
};
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// ======================
// 1. STUDENT CLASS WITH ENCAPSULATION
// ======================
class Student {
private:
// PRIVATE data members - cannot be accessed directly from outside
string name;
int age;
string studentID;
vector<double> grades;
static int totalStudents; // Static member
// Private helper method - internal use only
double calculateAverage() const {
if (grades.empty()) return 0.0;
double sum = 0.0;
for (double grade : grades) {
sum += grade;
}
return sum / grades.size();
}
public:
// Constructor
Student(string n, int a, string id) : name(n), age(a), studentID(id) {
totalStudents++;
cout << "Student " << name << " created. Total students: "
<< totalStudents << endl;
}
// Destructor
~Student() {
totalStudents--;
cout << "Student " << name << " removed. Total students: "
<< totalStudents << endl;
}
// ======================
// PUBLIC INTERFACE (Getters and Setters)
// ======================
// Getter for name (read-only access)
string getName() const {
return name;
}
// Getter for age
int getAge() const {
return age;
}
// Setter for age with validation
void setAge(int newAge) {
if (newAge >= 0 && newAge <= 120) {
age = newAge;
cout << "Age updated to " << age << endl;
} else {
cout << "Invalid age! Age must be between 0 and 120." << endl;
}
}
// Getter for studentID (read-only)
string getStudentID() const {
return studentID;
}
// Method to add grade with validation
void addGrade(double grade) {
if (grade >= 0.0 && grade <= 100.0) {
grades.push_back(grade);
cout << "Grade " << grade << " added for " << name << endl;
} else {
cout << "Invalid grade! Grade must be between 0 and 100." << endl;
}
}
// Get grades (returns copy for encapsulation)
vector<double> getGrades() const {
return grades; // Returns copy, not reference
}
// Get average grade (calls private helper)
double getAverageGrade() const {
return calculateAverage();
}
// Get letter grade based on average
char getLetterGrade() const {
double avg = getAverageGrade();
if (avg >= 90.0) return 'A';
else if (avg >= 80.0) return 'B';
else if (avg >= 70.0) return 'C';
else if (avg >= 60.0) return 'D';
else return 'F';
}
// Display student information
void displayInfo() const {
cout << "\n=== Student Information ===" << endl;
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "Student ID: " << studentID << endl;
cout << "Number of grades: " << grades.size() << endl;
if (!grades.empty()) {
cout << "Grades: ";
for (double grade : grades) {
cout << grade << " ";
}
cout << endl;
cout << "Average: " << getAverageGrade() << endl;
cout << "Letter Grade: " << getLetterGrade() << endl;
}
cout << "===========================\n" << endl;
}
// Static method to get total students
static int getTotalStudents() {
return totalStudents;
}
};
// Initialize static member
int Student::totalStudents = 0;
// ======================
// 2. COURSE CLASS DEMONSTRATING ENCAPSULATION
// ======================
class Course {
private:
string courseName;
string courseCode;
int maxStudents;
vector<Student*> enrolledStudents; // Using pointers for demonstration
public:
Course(string name, string code, int max)
: courseName(name), courseCode(code), maxStudents(max) {
cout << "Course " << courseName << " created" << endl;
}
// Enroll a student with capacity check
bool enrollStudent(Student* student) {
if (enrolledStudents.size() < maxStudents) {
enrolledStudents.push_back(student);
cout << student->getName() << " enrolled in " << courseName << endl;
return true;
} else {
cout << "Course is full! Cannot enroll " << student->getName() << endl;
return false;
}
}
// Display course information
void displayCourseInfo() const {
cout << "\n=== Course Information ===" << endl;
cout << "Course Name: " << courseName << endl;
cout << "Course Code: " << courseCode << endl;
cout << "Max Students: " << maxStudents << endl;
cout << "Currently Enrolled: " << enrolledStudents.size() << endl;
if (!enrolledStudents.empty()) {
cout << "Enrolled Students:" << endl;
for (const Student* student : enrolledStudents) {
cout << " - " << student->getName()
<< " (ID: " << student->getStudentID() << ")" << endl;
}
}
cout << "==========================\n" << endl;
}
// Getter methods
string getCourseName() const { return courseName; }
string getCourseCode() const { return courseCode; }
int getEnrollmentCount() const { return enrolledStudents.size(); }
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== ENCAPSULATION DEMONSTRATION: STUDENT MANAGEMENT SYSTEM ===" << endl << endl;
cout << "Initial total students: " << Student::getTotalStudents() << endl;
// ======================
// CREATING STUDENT OBJECTS
// ======================
cout << "\n1. Creating student objects:" << endl;
Student student1("Alice Johnson", 20, "S1001");
Student student2("Bob Smith", 22, "S1002");
Student student3("Charlie Brown", 21, "S1003");
cout << "\nCurrent total students: " << Student::getTotalStudents() << endl;
// ======================
// ACCESSING DATA THROUGH PUBLIC INTERFACE
// ======================
cout << "\n2. Accessing student data through public methods:" << endl;
// Using getters
cout << "Student 1 Name: " << student1.getName() << endl;
cout << "Student 1 Age: " << student1.getAge() << endl;
cout << "Student 1 ID: " << student1.getStudentID() << endl;
// Using setters with validation
cout << "\n3. Modifying data through setters:" << endl;
student1.setAge(21); // Valid age
student1.setAge(150); // Invalid age - will be rejected
// ======================
// WORKING WITH GRADES
// ======================
cout << "\n4. Adding grades with validation:" << endl;
student1.addGrade(95.5); // Valid grade
student1.addGrade(88.0); // Valid grade
student1.addGrade(105.0); // Invalid grade - will be rejected
student1.addGrade(-10.0); // Invalid grade - will be rejected
student2.addGrade(75.0);
student2.addGrade(82.5);
student2.addGrade(90.0);
// Display student information
student1.displayInfo();
student2.displayInfo();
student3.displayInfo();
// ======================
// DEMONSTRATING ENCAPSULATION PROTECTION
// ======================
cout << "\n5. Attempting to access private members (commented out):" << endl;
// These lines would cause COMPILATION ERRORS:
// student1.name = "Hacked Name"; // Error: 'name' is private
// student1.age = -10; // Error: 'age' is private
// student1.grades.push_back(100.0); // Error: 'grades' is private
cout << "Private members are protected by encapsulation!" << endl;
// ======================
// COURSE ENROLLMENT DEMONSTRATION
// ======================
cout << "\n6. Course enrollment system:" << endl;
Course mathCourse("Mathematics", "MATH101", 2);
Course csCourse("Computer Science", "CS101", 3);
// Enroll students
mathCourse.enrollStudent(&student1);
mathCourse.enrollStudent(&student2);
mathCourse.enrollStudent(&student3); // This should fail (course full)
csCourse.enrollStudent(&student1);
csCourse.enrollStudent(&student2);
csCourse.enrollStudent(&student3);
// Display course information
mathCourse.displayCourseInfo();
csCourse.displayCourseInfo();
// ======================
// WORKING WITH CONST OBJECTS
// ======================
cout << "\n7. Working with const objects:" << endl;
const Student constStudent("David Wilson", 23, "S1004");
// Can only call const member functions
cout << "Const student name: " << constStudent.getName() << endl;
cout << "Const student age: " << constStudent.getAge() << endl;
// These would cause compilation errors:
// constStudent.setAge(24); // Error: setAge() is not const
// constStudent.addGrade(85.0); // Error: addGrade() is not const
constStudent.displayInfo(); // displayInfo() is const, so it's allowed
// ======================
// SCOPE DEMONSTRATION
// ======================
cout << "\n8. Scope demonstration:" << endl;
{
cout << "Entering inner scope..." << endl;
Student tempStudent("Temporary Student", 19, "S9999");
tempStudent.displayInfo();
cout << "Exiting inner scope (destructor will be called)..." << endl;
}
cout << "\nFinal total students: " << Student::getTotalStudents() << endl;
cout << "\n===========================================" << endl << endl;
cout << "ENCAPSULATION BENEFITS DEMONSTRATED:" << endl;
cout << "1. Data Protection: Private members are safe from external modification" << endl;
cout << "2. Validation: Setters can validate data before assignment" << endl;
cout << "3. Flexibility: Internal implementation can change without affecting users" << endl;
cout << "4. Controlled Access: Public interface controls how data is accessed" << endl;
cout << "5. Maintainability: Changes are localized to the class" << endl;
return 0;
}
Encapsulation Benefits
- Data Hiding: Internal representation is hidden from outside
- Increased Security: Controlled access through public methods
- Flexibility & Maintainability: Can change internal implementation without affecting code that uses the class
- Data Validation: Setters can validate data before storing
- Modularity: Classes can be developed and tested independently
4. Inheritance: Code Reusability and Hierarchy
Inheritance allows a new class (derived class) to inherit properties and behaviors from an existing class (base class). This promotes code reusability and establishes relationships between classes.
Inheritance Syntax
// BASE CLASS (Parent)
class BaseClass {
protected:
int protectedData;
public:
void baseMethod() { }
};
// DERIVED CLASS (Child) - Single Inheritance
class DerivedClass : public BaseClass {
public:
void derivedMethod() {
protectedData = 10; // Can access protected members
baseMethod(); // Can access public methods
}
};
// MULTIPLE INHERITANCE
class MultiDerived : public Base1, public Base2 {
// Inherits from both Base1 and Base2
};
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// ======================
// 1. BASE CLASS: VEHICLE
// ======================
class Vehicle {
protected:
// Protected members - accessible in derived classes
string brand;
string model;
int year;
double price;
public:
// Constructor
Vehicle(string b, string m, int y, double p)
: brand(b), model(m), year(y), price(p) {
cout << "Vehicle constructor: " << brand << " " << model << endl;
}
// Virtual destructor (IMPORTANT for polymorphism)
virtual ~Vehicle() {
cout << "Vehicle destructor: " << brand << " " << model << endl;
}
// Public methods
virtual void displayInfo() const {
cout << "\n=== Vehicle Information ===" << endl;
cout << "Brand: " << brand << endl;
cout << "Model: " << model << endl;
cout << "Year: " << year << endl;
cout << "Price: $" << price << endl;
}
// Pure virtual function (makes Vehicle abstract)
virtual void start() const = 0; // Pure virtual
// Getter methods
string getBrand() const { return brand; }
string getModel() const { return model; }
int getYear() const { return year; }
double getPrice() const { return price; }
// Setter methods
void setPrice(double newPrice) {
if (newPrice > 0) {
price = newPrice;
}
}
};
// ======================
// 2. DERIVED CLASS: CAR (Single Inheritance)
// ======================
class Car : public Vehicle {
private:
int doors;
string fuelType;
public:
// Constructor
Car(string b, string m, int y, double p, int d, string f)
: Vehicle(b, m, y, p), doors(d), fuelType(f) {
cout << "Car constructor: " << brand << " " << model << endl;
}
// Override displayInfo() - polymorphism
void displayInfo() const override {
Vehicle::displayInfo(); // Call base class method
cout << "Doors: " << doors << endl;
cout << "Fuel Type: " << fuelType << endl;
cout << "Type: Car" << endl;
}
// Implement pure virtual function
void start() const override {
cout << brand << " " << model << " car starting... Vroom Vroom!" << endl;
}
// Car-specific method
void honk() const {
cout << brand << " " << model << " says: Beep Beep!" << endl;
}
// Getter methods
int getDoors() const { return doors; }
string getFuelType() const { return fuelType; }
};
// ======================
// 3. DERIVED CLASS: MOTORCYCLE
// ======================
class Motorcycle : public Vehicle {
private:
bool hasSidecar;
string engineType;
public:
// Constructor
Motorcycle(string b, string m, int y, double p, bool sidecar, string engine)
: Vehicle(b, m, y, p), hasSidecar(sidecar), engineType(engine) {
cout << "Motorcycle constructor: " << brand << " " << model << endl;
}
// Override displayInfo()
void displayInfo() const override {
Vehicle::displayInfo();
cout << "Has Sidecar: " << (hasSidecar ? "Yes" : "No") << endl;
cout << "Engine Type: " << engineType << endl;
cout << "Type: Motorcycle" << endl;
}
// Implement pure virtual function
void start() const override {
cout << brand << " " << model << " motorcycle starting... Vroom!" << endl;
}
// Motorcycle-specific method
void wheelie() const {
cout << brand << " " << model << " is doing a wheelie!" << endl;
}
};
// ======================
// 4. MULTILEVEL INHERITANCE: ELECTRIC CAR
// ======================
class ElectricCar : public Car {
private:
double batteryCapacity; // kWh
int range; // miles
public:
// Constructor
ElectricCar(string b, string m, int y, double p, int d,
double battery, int r)
: Car(b, m, y, p, d, "Electric"),
batteryCapacity(battery), range(r) {
cout << "ElectricCar constructor: " << brand << " " << model << endl;
}
// Override displayInfo()
void displayInfo() const override {
Car::displayInfo(); // Call parent class method
cout << "Battery Capacity: " << batteryCapacity << " kWh" << endl;
cout << "Range: " << range << " miles" << endl;
cout << "Subtype: Electric Car" << endl;
}
// Override start() method
void start() const override {
cout << brand << " " << model << " electric car starting... Silent Whirr!" << endl;
}
// ElectricCar-specific method
void chargeBattery() const {
cout << brand << " " << model << " is charging..." << endl;
cout << "Battery: " << batteryCapacity << " kWh" << endl;
cout << "Range: " << range << " miles" << endl;
}
};
// ======================
// 5. MULTIPLE INHERITANCE: AMPHIBIOUS VEHICLE
// ======================
class Boat {
protected:
double length; // in feet
string hullMaterial;
public:
Boat(double l, string material) : length(l), hullMaterial(material) {}
void floatOnWater() const {
cout << "Floating on water..." << endl;
}
void displayBoatInfo() const {
cout << "Boat Length: " << length << " feet" << endl;
cout << "Hull Material: " << hullMaterial << endl;
}
};
class AmphibiousVehicle : public Car, public Boat {
private:
bool isInWater;
public:
// Constructor - must initialize both parent classes
AmphibiousVehicle(string b, string m, int y, double p, int d,
double boatLength, string material)
: Car(b, m, y, p, d, "Hybrid"),
Boat(boatLength, material),
isInWater(false) {
cout << "AmphibiousVehicle constructor: " << brand << " " << model << endl;
}
// Override displayInfo() - need to resolve ambiguity
void displayInfo() const override {
cout << "\n=== Amphibious Vehicle Information ===" << endl;
cout << "Brand: " << Car::getBrand() << endl; // Specify which parent
cout << "Model: " << Car::getModel() << endl;
cout << "Year: " << Car::getYear() << endl;
cout << "Price: $" << Car::getPrice() << endl;
cout << "Doors: " << getDoors() << endl;
cout << "Fuel Type: " << getFuelType() << endl;
Boat::displayBoatInfo();
cout << "Currently: " << (isInWater ? "In Water" : "On Land") << endl;
}
// Override start()
void start() const override {
if (isInWater) {
cout << "Starting amphibious vehicle in water mode..." << endl;
floatOnWater();
} else {
cout << "Starting amphibious vehicle in land mode..." << endl;
Car::honk();
}
}
// Switch between land and water
void enterWater() {
isInWater = true;
cout << Car::getBrand() << " " << Car::getModel() << " entered water" << endl;
}
void exitWater() {
isInWater = false;
cout << Car::getBrand() << " " << Car::getModel() << " exited to land" << endl;
}
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== INHERITANCE DEMONSTRATION: VEHICLE HIERARCHY ===" << endl << endl;
// ======================
// SINGLE INHERITANCE
// ======================
cout << "1. Single Inheritance (Car from Vehicle):" << endl;
Car myCar("Toyota", "Camry", 2022, 25000.00, 4, "Gasoline");
myCar.displayInfo();
myCar.start();
myCar.honk();
cout << "\n===========================================" << endl << endl;
// ======================
// POLYMORPHISM THROUGH BASE POINTER
// ======================
cout << "2. Polymorphism using base class pointer:" << endl;
Vehicle* vehiclePtr;
// Point to Car object
vehiclePtr = &myCar;
cout << "\nVehicle pointer pointing to Car:" << endl;
vehiclePtr->displayInfo(); // Calls Car's displayInfo()
vehiclePtr->start(); // Calls Car's start()
// Point to Motorcycle object
Motorcycle myBike("Harley", "Davidson", 2021, 15000.00, false, "V-Twin");
vehiclePtr = &myBike;
cout << "\nVehicle pointer pointing to Motorcycle:" << endl;
vehiclePtr->displayInfo(); // Calls Motorcycle's displayInfo()
vehiclePtr->start(); // Calls Motorcycle's start()
cout << "\n===========================================" << endl << endl;
// ======================
// MULTILEVEL INHERITANCE
// ======================
cout << "3. Multilevel Inheritance (ElectricCar from Car):" << endl;
ElectricCar tesla("Tesla", "Model 3", 2023, 45000.00, 4, 75.0, 315);
tesla.displayInfo();
tesla.start();
tesla.honk(); // Inherited from Car
tesla.chargeBattery(); // ElectricCar specific
cout << "\n===========================================" << endl << endl;
// ======================
// MULTIPLE INHERITANCE
// ======================
cout << "4. Multiple Inheritance (AmphibiousVehicle from Car and Boat):" << endl;
AmphibiousVehicle amphib("WaterCar", "Amphib-X", 2023, 100000.00, 2, 15.5, "Fiberglass");
amphib.displayInfo();
// Start on land
amphib.start();
// Enter water and start
amphib.enterWater();
amphib.start();
// Exit water
amphib.exitWater();
cout << "\n===========================================" << endl << endl;
// ======================
// ARRAY OF BASE POINTERS
// ======================
cout << "5. Array of base class pointers (Polymorphism in action):" << endl;
vector<Vehicle*> vehicleFleet;
// Create different types of vehicles
vehicleFleet.push_back(new Car("Honda", "Civic", 2022, 22000.00, 4, "Gasoline"));
vehicleFleet.push_back(new Motorcycle("Yamaha", "R1", 2023, 18000.00, false, "Inline-4"));
vehicleFleet.push_back(new ElectricCar("Ford", "Mustang Mach-E", 2023, 50000.00, 4, 88.0, 305));
vehicleFleet.push_back(new AmphibiousVehicle("Amphi", "Rover", 2022, 120000.00, 4, 18.0, "Aluminum"));
// Process all vehicles polymorphically
cout << "\nProcessing vehicle fleet:" << endl;
for (Vehicle* v : vehicleFleet) {
cout << "\n-----------------------------" << endl;
v->displayInfo();
v->start();
cout << "-----------------------------" << endl;
}
// ======================
// ACCESS CONTROL DEMONSTRATION
// ======================
cout << "\n6. Access Control in Inheritance:" << endl;
// Public inheritance: public stays public, protected stays protected
cout << "Car (publicly inherited from Vehicle):" << endl;
cout << "Public members accessible: ";
cout << myCar.getBrand() << " " << myCar.getModel() << endl;
// These would cause compilation errors:
// myCar.brand = "Hacked"; // Error: brand is protected
// myCar.year = 2025; // Error: year is protected
// myCar.price = 10000; // Error: price is protected
cout << "\n===========================================" << endl << endl;
// ======================
// CLEANUP
// ======================
cout << "7. Cleanup (calling destructors):" << endl;
// Delete dynamically allocated objects
for (Vehicle* v : vehicleFleet) {
delete v; // Virtual destructor ensures proper cleanup
}
vehicleFleet.clear();
cout << "\n===========================================" << endl << endl;
cout << "INHERITANCE TYPES SUMMARY:" << endl;
cout << "1. Single: One base class, one derived class" << endl;
cout << "2. Multilevel: Derived class becomes base for another" << endl;
cout << "3. Multiple: Derived from multiple base classes" << endl;
cout << "4. Hierarchical: Multiple classes derived from one base" << endl;
cout << "5. Hybrid: Combination of multiple and multilevel" << endl;
cout << "\nINHERITANCE ACCESS SPECIFIERS:" << endl;
cout << "- public: public→public, protected→protected" << endl;
cout << "- protected: public→protected, protected→protected" << endl;
cout << "- private: public→private, protected→private" << endl;
return 0;
}
Types of Inheritance
- Single: One base, one derived
- Multiple: Multiple base classes
- Multilevel: Chain of inheritance
- Hierarchical: Multiple derived from one base
- Hybrid: Combination of above
Access Specifiers in Inheritance
- public: Most common, "is-a" relationship
- protected: Less common, intermediate access
- private: Rare, "implemented-in-terms-of"
- Affects how base members are inherited
Important Rules
- Always make destructor virtual in base class
- Use public inheritance for "is-a" relationships
- Avoid multiple inheritance when possible
- Use virtual inheritance to solve diamond problem
- Initialize all base classes in constructor
5. Polymorphism: One Interface, Multiple Forms
Polymorphism allows objects of different classes to be treated as objects of a common base class. It enables one interface to be used for a general class of actions.
Polymorphism Implementation
// BASE CLASS with virtual function
class Base {
public:
virtual void show() { // Virtual function
cout << "Base show()" << endl;
}
virtual void display() = 0; // Pure virtual function (abstract)
virtual ~Base() {} // Virtual destructor
};
// DERIVED CLASS overriding virtual function
class Derived : public Base {
public:
void show() override { // Override keyword (C++11)
cout << "Derived show()" << endl;
}
void display() override {
cout << "Derived display()" << endl;
}
};
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
using namespace std;
// ======================
// 1. ABSTRACT BASE CLASS: EMPLOYEE
// ======================
class Employee {
protected:
string name;
int id;
double baseSalary;
public:
// Constructor
Employee(string n, int i, double salary)
: name(n), id(i), baseSalary(salary) {}
// Virtual destructor (CRITICAL for polymorphism)
virtual ~Employee() {
cout << "Employee destructor: " << name << endl;
}
// Pure virtual function - makes Employee abstract
virtual double calculateSalary() const = 0;
// Virtual function with implementation
virtual void displayInfo() const {
cout << "\n=== Employee Information ===" << endl;
cout << "Name: " << name << endl;
cout << "ID: " << id << endl;
cout << "Base Salary: $" << fixed << setprecision(2) << baseSalary << endl;
}
// Non-virtual function
string getName() const {
return name;
}
int getID() const {
return id;
}
};
// ======================
// 2. DERIVED CLASS: FULL-TIME EMPLOYEE
// ======================
class FullTimeEmployee : public Employee {
private:
double bonus;
int yearsOfService;
public:
FullTimeEmployee(string n, int i, double salary, double b, int years)
: Employee(n, i, salary), bonus(b), yearsOfService(years) {}
// Override pure virtual function
double calculateSalary() const override {
// Full-time: base + bonus + (years * 1000)
return baseSalary + bonus + (yearsOfService * 1000.0);
}
// Override virtual function
void displayInfo() const override {
Employee::displayInfo();
cout << "Type: Full-Time Employee" << endl;
cout << "Bonus: $" << bonus << endl;
cout << "Years of Service: " << yearsOfService << endl;
cout << "Total Salary: $" << calculateSalary() << endl;
}
// FullTimeEmployee specific method
void giveRaise(double amount) {
baseSalary += amount;
cout << name << " received a raise of $" << amount << endl;
}
};
// ======================
// 3. DERIVED CLASS: PART-TIME EMPLOYEE
// ======================
class PartTimeEmployee : public Employee {
private:
int hoursWorked;
double hourlyRate;
public:
PartTimeEmployee(string n, int i, double rate, int hours)
: Employee(n, i, 0), hourlyRate(rate), hoursWorked(hours) {}
// Override pure virtual function
double calculateSalary() const override {
// Part-time: hours * rate
return hoursWorked * hourlyRate;
}
// Override virtual function
void displayInfo() const override {
Employee::displayInfo();
cout << "Type: Part-Time Employee" << endl;
cout << "Hourly Rate: $" << hourlyRate << endl;
cout << "Hours Worked: " << hoursWorked << endl;
cout << "Total Salary: $" << calculateSalary() << endl;
}
// PartTimeEmployee specific method
void addHours(int additionalHours) {
hoursWorked += additionalHours;
cout << name << " worked " << additionalHours << " more hours" << endl;
}
};
// ======================
// 4. DERIVED CLASS: MANAGER (with extra functionality)
// ======================
class Manager : public FullTimeEmployee {
private:
string department;
int teamSize;
public:
Manager(string n, int i, double salary, double b, int years,
string dept, int team)
: FullTimeEmployee(n, i, salary, b, years),
department(dept), teamSize(team) {}
// Override calculateSalary()
double calculateSalary() const override {
// Manager: FullTime salary + (teamSize * 500)
return FullTimeEmployee::calculateSalary() + (teamSize * 500.0);
}
// Override displayInfo()
void displayInfo() const override {
FullTimeEmployee::displayInfo();
cout << "Role: Manager" << endl;
cout << "Department: " << department << endl;
cout << "Team Size: " << teamSize << endl;
cout << "Manager Bonus: $" << (teamSize * 500.0) << endl;
cout << "Final Salary: $" << calculateSalary() << endl;
}
// Manager specific method
void conductMeeting() const {
cout << name << " is conducting a meeting with "
<< teamSize << " team members" << endl;
}
};
// ======================
// 5. DERIVED CLASS: INTERN
// ======================
class Intern : public Employee {
private:
string university;
int duration; // in months
public:
Intern(string n, int i, double stipend, string uni, int dur)
: Employee(n, i, stipend), university(uni), duration(dur) {}
// Override calculateSalary() - interns get fixed stipend
double calculateSalary() const override {
return baseSalary; // Fixed stipend
}
// Override displayInfo()
void displayInfo() const override {
Employee::displayInfo();
cout << "Type: Intern" << endl;
cout << "University: " << university << endl;
cout << "Duration: " << duration << " months" << endl;
cout << "Stipend: $" << calculateSalary() << endl;
}
// Intern specific method
void extendInternship(int additionalMonths) {
duration += additionalMonths;
cout << name << "'s internship extended by "
<< additionalMonths << " months" << endl;
}
};
// ======================
// 6. FUNCTION DEMONSTRATING POLYMORPHISM
// ======================
void processEmployee(Employee* emp) {
cout << "\nProcessing employee: " << emp->getName() << endl;
emp->displayInfo();
cout << "Calculated Salary: $" << emp->calculateSalary() << endl;
}
// Function that works with Employee references
void giveYearEndBonus(Employee& emp) {
cout << "\nYear-end bonus processing for: " << emp.getName() << endl;
emp.displayInfo();
// Each employee type gets bonus differently (polymorphism)
}
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== POLYMORPHISM DEMONSTRATION: EMPLOYEE MANAGEMENT SYSTEM ===" << endl << endl;
cout << fixed << setprecision(2);
// ======================
// CREATING DIFFERENT TYPES OF EMPLOYEES
// ======================
cout << "1. Creating different types of employees:" << endl;
FullTimeEmployee ftEmp("Alice Johnson", 1001, 60000.00, 5000.00, 5);
PartTimeEmployee ptEmp("Bob Smith", 1002, 25.00, 80);
Manager mgrEmp("Charlie Brown", 1003, 80000.00, 10000.00, 8, "Engineering", 10);
Intern intEmp("David Wilson", 1004, 2000.00, "Tech University", 6);
// ======================
// DIRECT METHOD CALLS
// ======================
cout << "\n2. Direct method calls on each object:" << endl;
ftEmp.displayInfo();
ptEmp.displayInfo();
mgrEmp.displayInfo();
intEmp.displayInfo();
// ======================
// POLYMORPHISM USING BASE CLASS POINTERS
// ======================
cout << "\n3. Polymorphism using base class pointers:" << endl;
Employee* empPtr;
// Point to FullTimeEmployee
empPtr = &ftEmp;
cout << "\nEmployee pointer pointing to FullTimeEmployee:" << endl;
empPtr->displayInfo(); // Calls FullTimeEmployee's displayInfo()
cout << "Salary via pointer: $" << empPtr->calculateSalary() << endl;
// Point to PartTimeEmployee
empPtr = &ptEmp;
cout << "\nEmployee pointer pointing to PartTimeEmployee:" << endl;
empPtr->displayInfo(); // Calls PartTimeEmployee's displayInfo()
cout << "Salary via pointer: $" << empPtr->calculateSalary() << endl;
// ======================
// ARRAY OF BASE POINTERS
// ======================
cout << "\n4. Array of base class pointers (true polymorphism):" << endl;
vector<Employee*> companyEmployees;
// Add different types of employees to the vector
companyEmployees.push_back(new FullTimeEmployee("Eve Davis", 1005, 55000.00, 4000.00, 3));
companyEmployees.push_back(new PartTimeEmployee("Frank Miller", 1006, 20.00, 60));
companyEmployees.push_back(new Manager("Grace Lee", 1007, 75000.00, 8000.00, 6, "Marketing", 8));
companyEmployees.push_back(new Intern("Henry Taylor", 1008, 1500.00, "Business College", 3));
companyEmployees.push_back(new FullTimeEmployee("Ivy Chen", 1009, 65000.00, 6000.00, 7));
// Process all employees polymorphically
cout << "\nProcessing all employees in company:" << endl;
cout << "=====================================" << endl;
double totalPayroll = 0.0;
for (Employee* emp : companyEmployees) {
cout << "\n";
emp->displayInfo();
double salary = emp->calculateSalary();
totalPayroll += salary;
cout << "Monthly Salary: $" << salary << endl;
cout << "-------------------------------------" << endl;
}
cout << "\nTotal Monthly Payroll: $" << totalPayroll << endl;
cout << "=====================================" << endl;
// ======================
// FUNCTION DEMONSTRATING POLYMORPHISM
// ======================
cout << "\n5. Functions working with base class pointers/references:" << endl;
processEmployee(&ftEmp);
processEmployee(&mgrEmp);
// Using references
giveYearEndBonus(ptEmp);
giveYearEndBonus(intEmp);
// ======================
// TYPEID AND DYNAMIC_CAST
// ======================
cout << "\n6. Runtime Type Identification (RTTI):" << endl;
for (Employee* emp : companyEmployees) {
cout << "\nEmployee: " << emp->getName() << endl;
// Using typeid
cout << "Type: " << typeid(*emp).name() << endl;
// Using dynamic_cast to check specific types
if (FullTimeEmployee* ft = dynamic_cast<FullTimeEmployee*>(emp)) {
cout << "This is a FullTimeEmployee (or derived from it)" << endl;
// Can call FullTimeEmployee specific methods
}
if (Manager* mgr = dynamic_cast<Manager*>(emp)) {
cout << "This is a Manager!" << endl;
mgr->conductMeeting();
}
if (Intern* intern = dynamic_cast<Intern*>(emp)) {
cout << "This is an Intern from " << intern->getName() << endl;
}
}
// ======================
// VIRTUAL DESTRUCTOR DEMONSTRATION
// ======================
cout << "\n7. Virtual destructor demonstration:" << endl;
Employee* testEmp = new Manager("Test Manager", 9999, 90000.00, 10000.00,
10, "Testing", 5);
cout << "\nCreated Manager object via Employee pointer" << endl;
testEmp->displayInfo();
cout << "\nDeleting via Employee pointer..." << endl;
delete testEmp; // Virtual destructor ensures Manager destructor is called
// ======================
// ABSTRACT CLASS DEMONSTRATION
// ======================
cout << "\n8. Abstract class demonstration:" << endl;
// This would cause compilation error:
// Employee abstractEmp("Abstract", 0000, 0); // Error: cannot instantiate abstract class
cout << "Employee is an abstract class (has pure virtual function)" << endl;
cout << "You can only create objects of concrete derived classes" << endl;
// ======================
// CLEANUP
// ======================
cout << "\n9. Cleaning up dynamically allocated memory:" << endl;
for (Employee* emp : companyEmployees) {
delete emp; // Virtual destructor ensures proper cleanup
}
companyEmployees.clear();
cout << "\n===========================================" << endl << endl;
cout << "POLYMORPHISM KEY CONCEPTS:" << endl;
cout << "1. Compile-time (Static) Polymorphism:" << endl;
cout << " - Function overloading" << endl;
cout << " - Operator overloading" << endl;
cout << " - Templates" << endl;
cout << endl;
cout << "2. Runtime (Dynamic) Polymorphism:" << endl;
cout << " - Virtual functions" << endl;
cout << " - Function overriding" << endl;
cout << " - Achieved through inheritance and pointers/references" << endl;
cout << endl;
cout << "3. Key Requirements:" << endl;
cout << " - Inheritance hierarchy" << endl;
cout << " - Virtual functions in base class" << endl;
cout << " - Base class pointers/references to derived objects" << endl;
cout << " - Virtual destructor in base class" << endl;
return 0;
}
Types of Polymorphism in C++
Compile-Time Polymorphism (Static)
- Function Overloading: Same name, different parameters
- Operator Overloading: Custom behavior for operators
- Templates: Generic programming
- Resolved at compile time
- Faster execution
Runtime Polymorphism (Dynamic)
- Virtual Functions: Function overriding
- Function Overriding: Derived class redefines base function
- Achieved through inheritance
- Resolved at runtime
- Flexible but slightly slower
- Always declare destructor as virtual in base class
- Use override keyword (C++11) to explicitly indicate overriding
- Use final keyword (C++11) to prevent further overriding
- Pure virtual functions make a class abstract (cannot instantiate)
- Virtual functions have small performance overhead (vtable lookup)
- Constructors cannot be virtual, destructors should be virtual
6. Abstraction: Hiding Complexity
Abstraction is the process of hiding complex implementation details and showing only essential features to the user. In C++, abstraction is achieved through abstract classes and interfaces.
Abstraction Implementation
// ABSTRACT CLASS (contains pure virtual function)
class AbstractClass {
public:
// Pure virtual function - makes class abstract
virtual void essentialOperation() = 0;
// Virtual destructor
virtual ~AbstractClass() {}
// Can have implemented methods too
void commonOperation() {
cout << "Common implementation" << endl;
}
};
// CONCRETE CLASS implementing abstraction
class ConcreteClass : public AbstractClass {
public:
void essentialOperation() override {
cout << "Concrete implementation" << endl;
}
};
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
// ======================
// 1. ABSTRACT INTERFACE: ELECTRONIC DEVICE
// ======================
class ElectronicDevice {
public:
// Pure virtual functions - interface contract
virtual void turnOn() = 0;
virtual void turnOff() = 0;
virtual void adjustSettings() = 0;
virtual string getStatus() const = 0;
// Virtual destructor
virtual ~ElectronicDevice() {
cout << "ElectronicDevice destroyed" << endl;
}
// Common non-virtual function
void displayDeviceInfo() const {
cout << "\nDevice Information:" << endl;
cout << "Status: " << getStatus() << endl;
}
};
// ======================
// 2. CONCRETE CLASS: TELEVISION
// ======================
class Television : public ElectronicDevice {
private:
string brand;
int channel;
int volume;
bool isOn;
public:
Television(string b) : brand(b), channel(1), volume(50), isOn(false) {}
// Implement interface methods
void turnOn() override {
isOn = true;
cout << brand << " TV is now ON" << endl;
cout << "Channel: " << channel << ", Volume: " << volume << endl;
}
void turnOff() override {
isOn = false;
cout << brand << " TV is now OFF" << endl;
}
void adjustSettings() override {
if (isOn) {
cout << "Adjusting " << brand << " TV settings..." << endl;
channel = (channel % 100) + 1;
volume = (volume + 10) % 100;
cout << "Changed to Channel: " << channel
<< ", Volume: " << volume << endl;
} else {
cout << "Cannot adjust - TV is OFF" << endl;
}
}
string getStatus() const override {
return isOn ? "ON (Channel: " + to_string(channel) +
", Volume: " + to_string(volume) + "%)" : "OFF";
}
// Television-specific methods
void changeChannel(int newChannel) {
if (isOn && newChannel > 0 && newChannel <= 999) {
channel = newChannel;
cout << "Changed to channel " << channel << endl;
}
}
};
// ======================
// 3. CONCRETE CLASS: AIR CONDITIONER
// ======================
class AirConditioner : public ElectronicDevice {
private:
string model;
int temperature;
string mode; // Cool, Heat, Fan, Dry
bool isOn;
public:
AirConditioner(string m, int temp = 24)
: model(m), temperature(temp), mode("Cool"), isOn(false) {}
// Implement interface methods
void turnOn() override {
isOn = true;
cout << model << " AC is now ON" << endl;
cout << "Mode: " << mode << ", Temperature: " << temperature << "°C" << endl;
}
void turnOff() override {
isOn = false;
cout << model << " AC is now OFF" << endl;
}
void adjustSettings() override {
if (isOn) {
cout << "Adjusting " << model << " AC settings..." << endl;
// Cycle through modes
if (mode == "Cool") mode = "Heat";
else if (mode == "Heat") mode = "Fan";
else if (mode == "Fan") mode = "Dry";
else mode = "Cool";
// Adjust temperature
temperature = (temperature % 30) + 16; // Between 16-30°C
cout << "Mode: " << mode << ", Temperature: " << temperature << "°C" << endl;
} else {
cout << "Cannot adjust - AC is OFF" << endl;
}
}
string getStatus() const override {
return isOn ? "ON (Mode: " + mode +
", Temp: " + to_string(temperature) + "°C)" : "OFF";
}
// AC-specific method
void setTemperature(int temp) {
if (isOn && temp >= 16 && temp <= 30) {
temperature = temp;
cout << "Temperature set to " << temperature << "°C" << endl;
}
}
};
// ======================
// 4. CONCRETE CLASS: SMART LIGHT
// ======================
class SmartLight : public ElectronicDevice {
private:
string location;
int brightness; // 0-100%
string color;
bool isOn;
public:
SmartLight(string loc, string col = "White")
: location(loc), brightness(50), color(col), isOn(false) {}
// Implement interface methods
void turnOn() override {
isOn = true;
cout << location << " light is now ON" << endl;
cout << "Brightness: " << brightness << "%, Color: " << color << endl;
}
void turnOff() override {
isOn = false;
cout << location << " light is now OFF" << endl;
}
void adjustSettings() override {
if (isOn) {
cout << "Adjusting " << location << " light settings..." << endl;
// Cycle through colors
if (color == "White") color = "Warm White";
else if (color == "Warm White") color = "Blue";
else if (color == "Blue") color = "Red";
else if (color == "Red") color = "Green";
else color = "White";
// Adjust brightness
brightness = (brightness + 25) % 100;
if (brightness == 0) brightness = 25;
cout << "Color: " << color << ", Brightness: " << brightness << "%" << endl;
} else {
cout << "Cannot adjust - Light is OFF" << endl;
}
}
string getStatus() const override {
return isOn ? "ON (Color: " + color +
", Brightness: " + to_string(brightness) + "%)" : "OFF";
}
// Light-specific method
void setBrightness(int level) {
if (isOn && level >= 0 && level <= 100) {
brightness = level;
cout << "Brightness set to " << brightness << "%" << endl;
}
}
};
// ======================
// 5. HOME AUTOMATION SYSTEM (Using Abstraction)
// ======================
class HomeAutomationSystem {
private:
vector<ElectronicDevice*> devices;
public:
void addDevice(ElectronicDevice* device) {
devices.push_back(device);
cout << "Device added to home automation system" << endl;
}
void turnOnAllDevices() {
cout << "\n=== Turning ON All Devices ===" << endl;
for (ElectronicDevice* device : devices) {
device->turnOn();
}
}
void turnOffAllDevices() {
cout << "\n=== Turning OFF All Devices ===" << endl;
for (ElectronicDevice* device : devices) {
device->turnOff();
}
}
void adjustAllSettings() {
cout << "\n=== Adjusting All Device Settings ===" << endl;
for (ElectronicDevice* device : devices) {
device->adjustSettings();
}
}
void displayAllStatus() {
cout << "\n=== All Device Status ===" << endl;
for (ElectronicDevice* device : devices) {
device->displayDeviceInfo();
}
}
// Cleanup
~HomeAutomationSystem() {
for (ElectronicDevice* device : devices) {
delete device;
}
devices.clear();
}
};
// ======================
// 6. FACTORY PATTERN USING ABSTRACTION
// ======================
class DeviceFactory {
public:
static ElectronicDevice* createDevice(string type, string name) {
if (type == "TV") {
return new Television(name);
} else if (type == "AC") {
return new AirConditioner(name);
} else if (type == "Light") {
return new SmartLight(name);
}
return nullptr;
}
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== ABSTRACTION DEMONSTRATION: HOME AUTOMATION SYSTEM ===" << endl << endl;
// ======================
// DIRECT USAGE OF CONCRETE CLASSES
// ======================
cout << "1. Direct usage of concrete classes:" << endl;
Television livingRoomTV("Samsung");
AirConditioner bedroomAC("LG");
SmartLight kitchenLight("Kitchen");
livingRoomTV.turnOn();
livingRoomTV.adjustSettings();
livingRoomTV.displayDeviceInfo();
bedroomAC.turnOn();
bedroomAC.adjustSettings();
bedroomAC.displayDeviceInfo();
kitchenLight.turnOn();
kitchenLight.adjustSettings();
kitchenLight.displayDeviceInfo();
cout << "\n===========================================" << endl << endl;
// ======================
// ABSTRACTION THROUGH BASE POINTERS
// ======================
cout << "2. Abstraction using base class pointers:" << endl;
ElectronicDevice* devicePtr;
// Point to Television
devicePtr = &livingRoomTV;
cout << "\nDevice pointer (pointing to TV):" << endl;
devicePtr->turnOn();
devicePtr->adjustSettings();
cout << "Status: " << devicePtr->getStatus() << endl;
// Point to AirConditioner
devicePtr = &bedroomAC;
cout << "\nDevice pointer (pointing to AC):" << endl;
devicePtr->turnOn();
devicePtr->adjustSettings();
cout << "Status: " << devicePtr->getStatus() << endl;
cout << "\n===========================================" << endl << endl;
// ======================
// HOME AUTOMATION SYSTEM
// ======================
cout << "3. Home Automation System (Real abstraction in action):" << endl;
HomeAutomationSystem smartHome;
// Create devices using factory
smartHome.addDevice(DeviceFactory::createDevice("TV", "Sony Bravia"));
smartHome.addDevice(DeviceFactory::createDevice("AC", "Daikin Inverter"));
smartHome.addDevice(DeviceFactory::createDevice("Light", "Living Room"));
smartHome.addDevice(DeviceFactory::createDevice("Light", "Bedroom"));
// Control all devices through abstract interface
smartHome.turnOnAllDevices();
smartHome.displayAllStatus();
smartHome.adjustAllSettings();
smartHome.displayAllStatus();
smartHome.turnOffAllDevices();
cout << "\n===========================================" << endl << endl;
// ======================
// SMART POINTERS WITH ABSTRACTION
// ======================
cout << "4. Smart pointers with abstraction (Modern C++):" << endl;
vector<unique_ptr<ElectronicDevice>> smartDevices;
smartDevices.push_back(make_unique<Television>("Panasonic"));
smartDevices.push_back(make_unique<AirConditioner>("Hitachi", 22));
smartDevices.push_back(make_unique<SmartLight>("Bathroom", "Blue"));
for (auto& device : smartDevices) {
device->turnOn();
device->adjustSettings();
device->displayDeviceInfo();
device->turnOff();
}
// No need to manually delete - unique_ptr handles it!
cout << "\n===========================================" << endl << endl;
// ======================
// ABSTRACT CLASS DEMONSTRATION
// ======================
cout << "5. Abstract class demonstration:" << endl;
// This would cause compilation error:
// ElectronicDevice abstractDevice; // Error: cannot instantiate abstract class
cout << "ElectronicDevice is abstract (has pure virtual functions)" << endl;
cout << "We can only create pointers/references to it" << endl;
cout << "Concrete classes must implement all pure virtual functions" << endl;
cout << "\n===========================================" << endl << endl;
cout << "ABSTRACTION BENEFITS DEMONSTRATED:" << endl;
cout << "1. Hides Complexity: Users don't need to know internal implementation" << endl;
cout << "2. Provides Interface: Consistent interface for different devices" << endl;
cout << "3. Loose Coupling: System components are independent" << endl;
cout << "4. Extensibility: Easy to add new device types" << endl;
cout << "5. Maintainability: Changes to one device don't affect others" << endl;
cout << "\nABSTRACTION vs ENCAPSULATION:" << endl;
cout << "- Encapsulation: Hides data and implementation details" << endl;
cout << "- Abstraction: Hides complexity and shows only essentials" << endl;
cout << "- Encapsulation is about HOW, Abstraction is about WHAT" << endl;
cout << "- Encapsulation protects data, Abstraction simplifies interface" << endl;
return 0;
}
Abstraction Implementation
- Abstract classes (pure virtual functions)
- Interfaces (all pure virtual functions)
- Header files (.h) as abstraction
- Libraries and APIs
- Design Patterns (Factory, Strategy, etc.)
Abstraction vs Encapsulation
- Abstraction: Design level, what to show
- Encapsulation: Implementation level, how to hide
- Abstraction solves design problems
- Encapsulation solves implementation problems
- Both work together for good OOP design
When to Use Abstraction
- Creating libraries/frameworks
- Defining contracts/interfaces
- Hiding platform-specific code
- Creating plug-in architectures
- When implementation may change
7. OOP Best Practices and Design Principles
OOP Design Principles (SOLID)
- S - Single Responsibility: One class, one reason to change
- O - Open/Closed: Open for extension, closed for modification
- L - Liskov Substitution: Derived classes should substitute base classes
- I - Interface Segregation: Many specific interfaces better than one general
- D - Dependency Inversion: Depend on abstractions, not concretions
Common OOP Mistakes
- God classes (too many responsibilities)
- Deep inheritance hierarchies
- Not making destructor virtual in base class
- Using public data members
- Circular dependencies between classes
- Overusing inheritance when composition is better
- Not following Rule of Three/Five
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
// ======================
// BAD OOP PRACTICES
// ======================
// BAD: God class - does everything
class BadEmployeeSystem {
private:
// Mixed responsibilities
vector<string> employeeNames;
vector<double> salaries;
vector<string> departments;
vector<string> projects;
// Also handles payroll, reporting, etc.
public:
// Too many unrelated methods
void addEmployee(string name, double salary, string dept) { }
void calculatePayroll() { }
void generateReport() { }
void assignProject(string employee, string project) { }
void scheduleMeeting() { } // Not related to employee management!
// ... 50 more methods
};
// BAD: Deep inheritance hierarchy
class BadShape {
// Base class
};
class Bad2DShape : public BadShape {
// Level 1
};
class BadPolygon : public Bad2DShape {
// Level 2
};
class BadQuadrilateral : public BadPolygon {
// Level 3
};
class BadRectangle : public BadQuadrilateral {
// Level 4 - too deep!
};
// BAD: Public data members (breaks encapsulation)
class BadBankAccount {
public:
double balance; // BAD: Should be private!
string accountNumber;
// No validation, no protection
};
// ======================
// GOOD OOP PRACTICES
// ======================
// GOOD: Single Responsibility Principle
class Employee {
private:
string name;
int id;
double baseSalary;
public:
Employee(string n, int i, double s) : name(n), id(i), baseSalary(s) {}
string getName() const { return name; }
// Only employee-related methods
};
class PayrollCalculator {
public:
double calculatePay(const Employee& emp) {
// Single responsibility: calculate pay
return 0; // Simplified
}
};
class ReportGenerator {
public:
void generateEmployeeReport(const Employee& emp) {
// Single responsibility: generate reports
}
};
// GOOD: Composition over Inheritance
class Engine {
public:
void start() { cout << "Engine started" << endl; }
};
class Wheels {
public:
void rotate() { cout << "Wheels rotating" << endl; }
};
class Car {
private:
Engine engine; // Composition
Wheels wheels[4]; // Composition
string model;
public:
void startCar() {
engine.start();
for (auto& wheel : wheels) {
wheel.rotate();
}
cout << model << " is ready to go!" << endl;
}
};
// GOOD: Interface Segregation Principle
class IPrintable {
public:
virtual void print() const = 0;
virtual ~IPrintable() = default;
};
class IScannable {
public:
virtual void scan() = 0;
virtual ~IScannable() = default;
};
class IFaxable {
public:
virtual void fax() = 0;
virtual ~IFaxable() = default;
};
// Implement only needed interfaces
class BasicPrinter : public IPrintable {
public:
void print() const override {
cout << "Printing document..." << endl;
}
};
class AllInOnePrinter : public IPrintable, public IScannable, public IFaxable {
public:
void print() const override {
cout << "Printing document..." << endl;
}
void scan() override {
cout << "Scanning document..." << endl;
}
void fax() override {
cout << "Sending fax..." << endl;
}
};
// GOOD: Dependency Inversion Principle
class IMessageSender {
public:
virtual void sendMessage(string message) = 0;
virtual ~IMessageSender() = default;
};
class EmailSender : public IMessageSender {
public:
void sendMessage(string message) override {
cout << "Sending email: " << message << endl;
}
};
class SMSSender : public IMessageSender {
public:
void sendMessage(string message) override {
cout << "Sending SMS: " << message << endl;
}
};
class NotificationService {
private:
IMessageSender& sender; // Depends on abstraction
public:
NotificationService(IMessageSender& s) : sender(s) {}
void notify(string message) {
sender.sendMessage(message);
}
};
// GOOD: Liskov Substitution Principle
class Bird {
public:
virtual void fly() {
cout << "Flying..." << endl;
}
virtual ~Bird() = default;
};
class Sparrow : public Bird {
public:
void fly() override {
cout << "Sparrow flying gracefully..." << endl;
}
};
// Penguin can't fly, so shouldn't inherit from Bird with fly()
class Penguin {
public:
void swim() {
cout << "Penguin swimming..." << endl;
}
};
// ======================
// MAIN FUNCTION
// ======================
int main() {
cout << "=== GOOD vs BAD OOP DESIGN ===" << endl << endl;
cout << "1. Single Responsibility Principle:" << endl;
Employee emp("John", 101, 50000.00);
PayrollCalculator payroll;
ReportGenerator reports;
cout << "Employee: " << emp.getName() << endl;
cout << "Each class has one responsibility" << endl;
cout << "\n2. Composition over Inheritance:" << endl;
Car myCar;
myCar.startCar();
cout << "\n3. Interface Segregation:" << endl;
BasicPrinter basic;
basic.print();
AllInOnePrinter allInOne;
allInOne.print();
allInOne.scan();
allInOne.fax();
cout << "\n4. Dependency Inversion:" << endl;
EmailSender emailSender;
NotificationService emailService(emailSender);
emailService.notify("Hello via Email");
SMSSender smsSender;
NotificationService smsService(smsSender);
smsService.notify("Hello via SMS");
cout << "\n5. Liskov Substitution:" << endl;
Sparrow sparrow;
sparrow.fly();
Penguin penguin;
penguin.swim();
// penguin.fly(); // Wouldn't compile - good design!
cout << "\n===========================================" << endl << endl;
cout << "OOP BEST PRACTICES SUMMARY:" << endl;
cout << "1. Favor Composition over Inheritance" << endl;
cout << "2. Follow SOLID principles" << endl;
cout << "3. Keep classes small and focused" << endl;
cout << "4. Use const correctness" << endl;
cout << "5. Follow Rule of Three/Five/Zero" << endl;
cout << "6. Use smart pointers for resource management" << endl;
cout << "7. Make base class destructor virtual" << endl;
cout << "8. Use override and final keywords (C++11+)" << endl;
cout << "9. Prefer initialization lists in constructors" << endl;
cout << "10. Design for testability" << endl;
return 0;
}
Golden Rules of C++ OOP
Design Principles
- Encapsulate what varies
- Program to interfaces
- Favor composition
- Keep it simple
C++ Specific
- Virtual destructor in base class
- Rule of Three/Five/Zero
- Const correctness
- RAII (Resource Acquisition Is Initialization)
Modern C++ (C++11+)
- Use override and final
- Prefer smart pointers
- Use move semantics
- Use auto for type deduction