C++ Programming Tutorial

Learn modern C++ step-by-step — from basics to advanced features like templates, STL, and smart pointers. Clear explanations with practical, runnable examples.

C++ Inheritance and Polymorphism

Inheritance lets you create new classes from existing ones, reusing and extending their code. Polymorphism allows calling derived class methods through base class pointers/references, enabling flexible and extensible designs.

Basic Inheritance

inheritance.cpp
#include <iostream>
#include <string>

// Base class
class Animal {
protected:
    std::string name;
public:
    Animal(std::string n) : name(std::move(n)) {}
    void eat() const { std::cout << name << " is eating
"; }
    std::string get_name() const { return name; }
};

// Derived class — inherits from Animal
class Dog : public Animal {
    std::string breed;
public:
    Dog(std::string n, std::string b)
        : Animal(std::move(n)), breed(std::move(b)) {}

    void bark() const { std::cout << name << " says Woof!
"; }
    std::string get_breed() const { return breed; }
};

class Cat : public Animal {
public:
    Cat(std::string n) : Animal(std::move(n)) {}
    void purr() const { std::cout << name << " is purring
"; }
};

int main() {
    Dog rex("Rex", "Labrador");
    rex.eat();   // inherited from Animal
    rex.bark();  // Dog-specific

    Cat whiskers("Whiskers");
    whiskers.eat();
    whiskers.purr();

    return 0;
}

Virtual Functions and Polymorphism

polymorphism.cpp
#include <iostream>
#include <vector>
#include <memory>

class Shape {
public:
    virtual double area() const = 0;  // pure virtual — abstract class
    virtual std::string type() const = 0;
    virtual ~Shape() = default;  // virtual destructor!

    void describe() const {
        std::cout << type() << ": area = " << area() << "
";
    }
};

class Circle : public Shape {
    double radius;
public:
    Circle(double r) : radius(r) {}
    double area() const override { return 3.14159 * radius * radius; }
    std::string type() const override { return "Circle"; }
};

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
    std::string type() const override { return "Rectangle"; }
};

int main() {
    // Polymorphism: different types through base class pointer
    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));
    shapes.push_back(std::make_unique<Circle>(2.0));

    for (const auto& s : shapes) {
        s->describe();  // calls correct derived method!
    }
    // Circle: area = 78.5398
    // Rectangle: area = 12
    // Circle: area = 12.5664

    return 0;
}

Key Concepts

ConceptKeywordPurpose
Virtual functionvirtualEnables runtime dispatch based on actual object type
Pure virtual= 0Makes class abstract — cannot instantiate, must override
OverrideoverrideVerifies method actually overrides a base virtual (catches typos)
FinalfinalPrevents further overriding or inheritance
Virtual destructorvirtual ~Base()Ensures correct cleanup when deleting through base pointer

Access Control in Inheritance

access.cpp
class Base {
public:    int pub;     // accessible everywhere
protected: int prot;    // accessible in Base and Derived
private:   int priv;    // accessible only in Base
};

// public inheritance (most common) — preserves access levels
class Derived : public Base {
    void example() {
        pub = 1;   // OK — public in Base, public in Derived
        prot = 2;  // OK — protected in Base, protected in Derived
        // priv = 3; // ERROR — private in Base, inaccessible
    }
};

// Usage:
// Derived d;
// d.pub = 1;   // OK
// d.prot = 2;  // ERROR — protected from outside
// d.priv = 3;  // ERROR — private to Base

Best Practices

  • Always use override on derived virtual methods — catches errors at compile time.
  • Always make base destructors virtual when using polymorphism through pointers.
  • Prefer composition over inheritance for "has-a" relationships; use inheritance for "is-a" relationships.
  • Use std::unique_ptr<Base> for polymorphic ownership — safer than raw pointers.
  • Use pure virtual functions to define interfaces that derived classes must implement.
  • Avoid deep inheritance hierarchies — prefer flat, interface-based designs.

Keep Practicing

Use the online compiler to run every example and experiment with modifications. The best way to learn C++ is by writing code — even small programs build strong foundations.