Object-Oriented Programming in C++
Master C++ OOP concepts including inheritance, polymorphism, virtual functions, and modern object-oriented design principles with interactive examples.
Best viewed on desktop for optimal interactive experience
Object-Oriented Programming in C++
C++ supports multiple programming paradigms, with object-oriented programming being one of its core strengths. Understanding inheritance, polymorphism, and proper class design is crucial for effective C++ development.
The Four Pillars of OOP
- Encapsulation: Data hiding and access control
- Inheritance: Code reuse and "is-a" relationships
- Polymorphism: One interface, multiple implementations
- Abstraction: Hiding complexity, showing essentials
Object-Oriented Programming in C++
C++ supports multiple programming paradigms, with object-oriented programming being one of its core strengths. Understanding inheritance, polymorphism, and proper class design is crucial for effective C++ development.
Encapsulation
Data hiding and access control
Inheritance
Code reuse and is-a relationships
Polymorphism
One interface, multiple implementations
Abstraction
Hiding complexity, showing essentials
Interactive OOP Explorer
Without Virtual Functions
class Base {
public:
void method() {
std::cout << "Base method
";
}
};
class Derived : public Base {
public:
void method() {
std::cout << "Derived method
";
}
};
Base* ptr = new Derived();
ptr->method(); // Calls Base::method
// Output: "Base method"
With Virtual Functions
class Base {
public:
virtual void method() {
std::cout << "Base method
";
}
virtual ~Base() = default; // Always virtual!
};
class Derived : public Base {
public:
void method() override {
std::cout << "Derived method
";
}
};
Base* ptr = new Derived();
ptr->method(); // Calls Derived::method
// Output: "Derived method"
Polymorphism Example
#include <vector>
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0; // Pure virtual = abstract
virtual double area() const = 0;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}
double area() const override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
void draw() const override {
std::cout << "Drawing a rectangle " << width << "x" << height << std::endl;
}
double area() const override {
return width * height;
}
};
// Using polymorphism
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
double totalArea = 0;
for (const auto& shape : shapes) {
shape->draw(); // Calls correct draw() method
totalArea += shape->area(); // Calls correct area() method
}
std::cout << "Total area: " << totalArea << std::endl;
Modern C++ OOP Guidelines
- • Make destructors virtual in base classes
- • Use override keyword for virtual function overrides
- • Prefer composition over inheritance
- • Follow SOLID principles
- • Use smart pointers for polymorphic objects
- • Single Responsibility: One class, one reason to change
- • Open/Closed: Open for extension, closed for modification
- • Liskov Substitution: Subtypes must be substitutable
- • Interface Segregation: Many specific interfaces over one general
- • Dependency Inversion: Depend on abstractions, not concretions
Virtual Functions and Polymorphism
Runtime Polymorphism
Virtual functions enable runtime polymorphism, allowing the correct function to be called based on the actual object type, not the pointer type.
class Animal { public: virtual ~Animal() = default; virtual void makeSound() const = 0; // Pure virtual virtual void move() const { std::cout << "Animal is moving" << std::endl; } }; class Dog : public Animal { public: void makeSound() const override { std::cout << "Woof!" << std::endl; } void move() const override { std::cout << "Dog is running" << std::endl; } }; class Cat : public Animal { public: void makeSound() const override { std::cout << "Meow!" << std::endl; } }; // Usage std::vector<std::unique_ptr<Animal>> animals; animals.push_back(std::make_unique<Dog>()); animals.push_back(std::make_unique<Cat>()); for (const auto& animal : animals) { animal->makeSound(); // Calls correct implementation animal->move(); // Polymorphic behavior }
Virtual Destructor Rule
Always make destructors virtual in base classes to ensure proper cleanup of derived objects.
class Base { public: virtual ~Base() = default; // Essential for polymorphic classes };
Inheritance Types
Public Inheritance ("is-a")
Most common form - derived class is a type of base class.
class Vehicle { protected: std::string brand; public: Vehicle(std::string b) : brand(std::move(b)) {} virtual void start() = 0; }; class Car : public Vehicle { // Car IS-A Vehicle public: Car(std::string brand) : Vehicle(std::move(brand)) {} void start() override { std::cout << brand << " car engine started" << std::endl; } };
Private Inheritance ("implemented-in-terms-of")
Rarely used - for implementation details only.
class Timer { public: void start(); void stop(); bool isRunning() const; }; class Widget : private Timer { // Widget uses Timer internally public: void processData() { start(); // Can use Timer methods internally // ... processing logic ... stop(); } // Timer interface not exposed to Widget users };
Multiple Inheritance
C++ supports multiple inheritance, but it requires careful design.
class Flyable { public: virtual ~Flyable() = default; virtual void fly() = 0; }; class Swimmable { public: virtual ~Swimmable() = default; virtual void swim() = 0; }; class Duck : public Flyable, public Swimmable { public: void fly() override { std::cout << "Duck is flying!" << std::endl; } void swim() override { std::cout << "Duck is swimming!" << std::endl; } };
Diamond Problem Solution
Use virtual inheritance to solve the diamond problem.
class Animal { public: virtual ~Animal() = default; void breathe() { std::cout << "Breathing..." << std::endl; } }; class Mammal : virtual public Animal { public: void giveBirth() { std::cout << "Giving birth" << std::endl; } }; class Aquatic : virtual public Animal { public: void swimUnderwater() { std::cout << "Swimming underwater" << std::endl; } }; class Whale : public Mammal, public Aquatic { // Only one Animal base class instance };
Abstract Classes and Interfaces
Pure Virtual Functions
Create abstract base classes that cannot be instantiated.
class Shape { // Abstract base class public: virtual ~Shape() = default; // Pure virtual functions virtual double area() const = 0; virtual double perimeter() const = 0; virtual void draw() const = 0; // Concrete function virtual void displayInfo() const { std::cout << "Area: " << area() << ", Perimeter: " << perimeter() << std::endl; } }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } double perimeter() const override { return 2 * 3.14159 * radius; } void draw() const override { std::cout << "Drawing a circle with radius " << radius << std::endl; } };
Composition vs Inheritance
When to Use Composition
Prefer composition when you need "has-a" relationships.
class Engine { public: void start() { /* ... */ } void stop() { /* ... */ } int getHorsepower() const { return horsepower_; } private: int horsepower_ = 200; }; class Car { private: Engine engine_; // Composition: Car HAS-A Engine std::string model_; public: Car(std::string model) : model_(std::move(model)) {} void start() { engine_.start(); // Delegates to engine std::cout << model_ << " is starting" << std::endl; } int getPower() const { return engine_.getHorsepower(); } };
Benefits of Composition
- More flexible than inheritance
- Better encapsulation
- Easier to test and mock
- Avoids deep inheritance hierarchies
- Can change behavior at runtime
Modern C++ OOP Features
Override and Final Keywords
class Base { public: virtual void method() {} virtual void finalMethod() final {} // Cannot be overridden }; class Derived : public Base { public: void method() override {} // Explicit override // void finalMethod() override {} // Error: cannot override final }; class Final final : public Derived { // Cannot be inherited from // ... };
SOLID Principles in C++
- Single Responsibility: One class, one reason to change
- Open/Closed: Open for extension, closed for modification
- Liskov Substitution: Subtypes must be substitutable for base types
- Interface Segregation: Many specific interfaces over one general
- Dependency Inversion: Depend on abstractions, not concretions
Best Practices
Design Guidelines
- Use public inheritance for "is-a" relationships only
- Prefer composition over inheritance when possible
- Make destructors virtual in polymorphic base classes
- Use the
override
keyword for clarity - Follow the Rule of Zero/Three/Five
- Apply SOLID principles
Performance Considerations
- Virtual function calls have small overhead
- Multiple inheritance can increase object size
- Virtual inheritance has additional cost
- Consider template-based alternatives (CRTP) for compile-time polymorphism
C++ OOP provides powerful tools for modeling real-world relationships and creating maintainable, extensible code when used judiciously.