Virtual Functions & Abstract Classes

    Atul Kabra4 min readUpdated

    A virtual function is a member function that the program resolves at run time based on the object's real type, not the type of the pointer or reference used to call it. An abstract class is one that declares at least one pure virtual function — it defines an interface that derived classes must implement, and cannot itself be instantiated. Together they let you write code against a general interface while concrete subclasses fill in the behaviour.

    Let us build the pattern step by step.

    Virtual functions enable dynamic dispatch

    Mark a base function virtual, override it in a derived class, and call it through a base reference or pointer.

    #include <iostream>
    
    class Notification {
    public:
        virtual void send() const {       // virtual: resolved at run time
            std::cout << "Generic notification\n";
        }
        virtual ~Notification() = default; // virtual destructor (essential)
    };
    
    class EmailNotification : public Notification {
    public:
        void send() const override {      // override the base behaviour
            std::cout << "Sending email\n";
        }
    };
    
    void dispatch(const Notification& n) {
        n.send();   // calls the REAL object's send(), not the base's
    }
    
    int main() {
        EmailNotification e;
        dispatch(e);   // prints "Sending email"
    }
    

    Without virtual, dispatch would always print "Generic notification." The virtual keyword is what makes the call follow the true object type.

    Pure virtual functions and abstract classes

    Set a virtual function to = 0 to make it pure virtual — it has no body and must be overridden. A class with any pure virtual function is abstract and cannot be instantiated directly.

    #include <iostream>
    #include <memory>
    #include <vector>
    
    // abstract base = an interface
    class Shape {
    public:
        virtual double area() const = 0;   // pure virtual: no body here
        virtual void describe() const {    // virtual with a default body
            std::cout << "A shape with area " << area() << "\n";
        }
        virtual ~Shape() = default;
    };
    
    class Rectangle : public Shape {
        double w, h;
    public:
        Rectangle(double width, double height) : w(width), h(height) {}
        double area() const override { return w * h; }  // must implement
    };
    
    int main() {
        // Shape s;   // ERROR: cannot instantiate an abstract class
        std::unique_ptr<Shape> shape = std::make_unique<Rectangle>(3.0, 4.0);
        shape->describe();   // "A shape with area 12"
    }
    

    Shape is the contract: every shape promises an area(). Rectangle honours it. Code that takes a Shape& works with any present or future shape.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses

    Abstract classes as interfaces

    When every function in an abstract class is pure virtual and there is no data, it acts as a pure interface — the C++ equivalent of an "interface" in other languages.

    class Drawable {
    public:
        virtual void draw() const = 0;
        virtual ~Drawable() = default;
    };
    

    Any class can implement Drawable by overriding draw(), and code can treat all of them uniformly through Drawable&.

    The virtual destructor rule

    If you ever delete a derived object through a base pointer, the base must have a virtual destructor. Otherwise only the base part is destroyed — undefined behaviour and likely a leak. Make the destructor of any polymorphic base class virtual (often virtual ~Base() = default;). Smart pointers depend on this too.

    Common mistakes

    • Omitting the virtual destructor. Deleting through a base pointer without one corrupts or leaks. Always make polymorphic base destructors virtual.
    • Forgetting override. Without it, a typo in the signature silently creates a new function instead of overriding. override turns that into a compile error.
    • Trying to instantiate an abstract class. Shape s; fails to compile if Shape has a pure virtual function — that is the point. Use a pointer or reference to a concrete subclass.
    • Object slicing. Storing a derived object in a base value loses the derived part. Use references, pointers, or smart pointers for polymorphic code.

    FAQ

    What makes a class abstract? Having at least one pure virtual function (= 0). Such a class cannot be instantiated; it exists to be a base for concrete subclasses.

    Why does a base class need a virtual destructor? So that deleting a derived object through a base pointer runs the derived destructor too. Without it, the cleanup is incomplete and the behaviour is undefined.

    Keep learning

    Master interfaces and polymorphic design with mentors in Jalgaon — join the waitlist for our C++ Programming course.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses
    Atul Kabra

    Founder, Infoplanet

    Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.

    Related guides