C++ Abstract Class and Pure Virtual Function: Explained with Examples

C++ Abstract Class and Pure Virtual Function: Explained with Examples


C++ abstract class and pure virtual function are key building blocks for object-oriented design in C++. They let you define common behavior at a high level and force derived classes to implement specific functions. This page explains what they are, how they work, when to use them, and the most common mistakes beginners make, with clear, runnable examples.

Key takeaways: C++ abstract class and pure virtual function
  • An abstract class cannot be instantiated and has at least one pure virtual function (e.g., virtual void f() = 0;).
  • Use base references/pointers to enable runtime polymorphism across derived types.
  • Concrete derived classes must override every inherited pure virtual; use override to let the compiler verify the signature.
  • Always give polymorphic bases a virtual destructor; a pure virtual destructor still needs a definition.
  • Avoid calling virtual functions in constructors/destructors and avoid object slicing; prefer std::unique_ptr<Base> or references.

What is an abstract class in C++?

C++ abstract class hierarchy with a pure virtual function and derived classes overriding it.
Abstract Shape defines a pure virtual draw(); Circle and Rectangle must provide their own implementations.

An abstract class is a class that cannot be instantiated directly. In C++, a class becomes abstract when it declares (or inherits) at least one pure virtual function. You can still create pointers or references to an abstract class and use them to achieve runtime polymorphism, but you cannot create an object of the abstract type itself.

  • Declaring a pure virtual function: use = 0 in the declaration.
  • Abstract classes can have constructors, data members, and non-virtual functions.
  • They are often used as “interfaces” that define required behavior for derived classes.

Simple abstract class syntax

Code
#class="cd-package">include <iostream>

struct Shape {
    virtual void draw() const = 0; // pure virtual function
    virtual ~Shape() = default;    // always virtual destructor for polymorphic deletion
};

If you try to do Shape s;, the compiler will error, because Shape is abstract.

Pure virtual function in C++

Polymorphism in C++: calls via an abstract base pointer dispatch to Circle, Rectangle, and Triangle implementations.
Pure virtual functions enable dynamic dispatch: calling through Shape* runs each derived class’s override.

A pure virtual function in C++ is a virtual member function with no default implementation provided at the point of declaration, written with = 0. A class that has (or inherits) a pure virtual function is abstract.

  • Syntax: virtual ReturnType func(params) = 0;
  • You must override a pure virtual function in any concrete derived class to be instantiable.
  • Advanced: a pure virtual function can still have an out-of-class definition; the class remains abstract.

Abstract class example in C++ (beginner-friendly)

Here is a minimal example showing polymorphism with an abstract base class and two derived classes.

Code
#class="cd-package">include <iostream>
#class="cd-package">include <memory>
#class="cd-package">include <vector>

struct Shape {
    virtual void draw() const = 0;  // pure virtual
    virtual ~Shape() = default;     // virtual destructor
};

struct Circle : Shape {
    void draw() const override {
        std::cout << "Drawing Circlen";
    }
};

struct Rectangle : Shape {
    void draw() const override {
        std::cout << "Drawing Rectanglen";
    }
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>());
    shapes.push_back(std::make_unique<Rectangle>());

    for (const auto& s : shapes) {
        s->draw(); // runtime polymorphism
    }
}
Output
Drawing Circle
Drawing Rectangle

In this example, Shape is abstract because of the pure virtual function draw(). We store derived objects in a std::vector<std::unique_ptr<Shape>> and call draw() polymorphically.

How a pure virtual call resolves at runtime
1
You call a virtual function through a Base& or Base* (e.g., s->draw()).

2
At runtime, dispatch follows the object’s dynamic type (the most-derived class).

3
Implementations typically use a hidden pointer to a virtual table (vtable) for the object’s class.

4
The vtable entry for the function identifies the most-derived override.

5
The derived override executes.

Note: The C++ standard specifies dynamic dispatch semantics; vtable/vptr are common implementation techniques.

How to create abstract class in C++ step by step

  1. Declare a base class with at least one pure virtual function using = 0.
  2. Give the base class a virtual destructor (often defaulted).
  3. Derive a concrete class and implement (override) every pure virtual function.
  4. Use pointers or references to the base class to enable polymorphism.
Code
#class="cd-package">include <iostream>
#class="cd-package">include <memory>

class Animal {
class="cd-keyword cd-access">public:
    virtual void speak() const = 0; // Step 1: pure virtual
    virtual ~Animal() = default;    // Step 2: virtual destructor
};

class Dog : class="cd-keyword cd-access">public Animal {
class="cd-keyword cd-access">public:
    void speak() const override {   // Step 3: implement (override)
        std::cout << "Woof!n";
    }
};

int main() {
    std::unique_ptr<Animal> a = std::make_unique<Dog>(); // Step 4
    a->speak();
}
Output
Woof!

Overriding pure virtual functions in C++

When you override a pure virtual function in a derived class, prefer the override specifier. It lets the compiler check that you are actually overriding a virtual function from the base class.

Code
#class="cd-package">include <iostream>

struct Base {
    virtual void run() = 0;
    virtual ~Base() = default;
};

struct Derived final : Base { // ___CDPHSTR0___ prevents further inheritance
    void run() override {     // ___CDPHSTR1___ ensures correct signature
        std::cout << "Derived::runn";
    }
};

int main() {
    Derived d;
    Base& b = d;
    b.run();
}
Output
Derived::run

Tip: If you try to override run() in a class derived from Derived, the compiler will error because Derived is marked final.

Pure virtual function can still have a definition

This surprises many learners: a pure virtual function can have a body defined outside the class. The class remains abstract, but derived classes may call the base implementation explicitly.

Code
#class="cd-package">include <iostream>

struct Base {
    virtual void info() = 0;  // pure virtual
    virtual ~Base() = default;
};

void Base::info() {           // out-of-class definition for a pure virtual
    std::cout << "Base::info default behaviorn";
}

struct Derived : Base {
    void info() override {
        std::cout << "Derived: ";
        Base::info();         // call base's definition if useful
    }
};

int main() {
    Derived d;
    d.info();
}
Output
Derived: Base::info default behavior

Pure virtual destructor example

If you declare a pure virtual destructor, you must still provide a definition for it. This allows proper cleanup through base pointers.

Code
#class="cd-package">include <iostream>

struct Device {
    virtual ~Device() = 0; // pure virtual destructor
};

Device::~Device() {        // must provide a definition
    std::cout << "Device destroyedn";
}

struct Keyboard : Device {
    ~Keyboard() override {
        std::cout << "Keyboard destroyedn";
    }
};

int main() {
    Device* d = new Keyboard{};
    delete d; // calls Keyboard::~Keyboard, then Device::~Device
}
Output
Keyboard destroyed
Device destroyed

When to use abstract class in C++

Use an abstract class when:

  • You need runtime polymorphism across a family of types.
  • You want to define a common interface and force derived classes to implement required functions.
  • You will work with objects via base pointers/references (e.g., from a factory, plugin system, or strategy pattern).
  • You need dynamic dispatch that can be extended in separate translation units.

Alternatives:

  • Templates and C++20 concepts for compile-time polymorphism (no virtual functions involved).
  • Type erasure (e.g., “polymorphic value type”) when you want runtime polymorphism without inheritance.

Abstract base class vs concrete class in C++

Feature Abstract base class Concrete class
Instantiation Cannot be instantiated Can be instantiated
Pure virtual functions At least one None required
Use case Define interface and enable polymorphism Provide complete behavior
Ownership and deletion Delete via base pointer; base destructor should be virtual Normal deletion of the object

Difference between abstract class and interface in C++

C++ has no interface keyword. People often use “interface” to mean a class with only public pure virtual functions and a virtual destructor, and no data members.

Aspect Abstract class Interface-like class (C++ style)
Members May have data and function implementations Only pure virtual functions, no data
Purpose Can provide partial implementation and state Defines behavior contract only
Flexibility Good for base behavior + customization points Good for clean, minimal APIs

Common mistakes with abstract classes in C++

  • Forgetting the virtual destructor in a polymorphic base. Without it, deleting a derived object via a base pointer is undefined behavior.
  • Trying to instantiate the abstract base class. You must instantiate a concrete derived class.
  • Not marking overrides with override. This can hide bugs when signatures do not match perfectly.
  • Object slicing: passing/returning derived objects by value as the base type discards derived parts. Use pointers or references to the base.
  • Calling virtual functions (especially pure virtual) inside constructors or destructors of the base class. Dynamic dispatch does not work there; calling a pure virtual in this context is undefined behavior.
  • Expecting default arguments on virtual functions to dispatch dynamically. Default arguments are bound at compile time, not at runtime.
  • Trying to make a member function template virtual. In C++, member function templates cannot be virtual.

How do pure virtual functions work in C++? (Quick recap)

  • A pure virtual makes the class abstract.
  • Derived classes must override it to become concrete (instantiable).
  • Calls through base references/pointers resolve at runtime to the most-derived override.
  • A pure virtual may still have an out-of-class definition; the base remains abstract.

FAQ: C++ abstract class and pure virtual function

What is an abstract class in C++ with a simple example?

An abstract class has at least one pure virtual function and cannot be instantiated. Example: a Shape class with virtual void draw() = 0; and derived classes like Circle and Rectangle that implement draw().

How do pure virtual functions work in C++?

They declare an interface that derived classes must implement. Calls to them via base references/pointers dispatch to the derived implementation at runtime. The base may still provide an out-of-class definition, but that does not make the class concrete.

Can we instantiate an abstract class in C++?

No. You cannot create an object of an abstract class. You can, however, create pointers or references to it and bind them to objects of derived concrete classes.

When should I use an abstract class in C++?

Use it to define a common API for a family of types and to enable runtime polymorphism, such as in plugins, GUI widgets, shapes, or drivers. Prefer minimal interfaces and consider alternatives like templates or concepts when compile-time polymorphism suffices.

What happens if a pure virtual function is not overridden?

The derived class remains abstract and cannot be instantiated. The compiler will prevent you from creating objects of that class.

Best practices and tips

  • Keep abstract bases small: only the necessary pure virtuals and a virtual destructor.
  • Use override to catch signature mismatches; use final to stop further overrides.
  • Manage polymorphic objects via smart pointers (e.g., std::unique_ptr) to avoid memory leaks.
  • If the base owns resources or needs custom cleanup, ensure the destructor is virtual; a pure virtual destructor must still have a definition.
Practical checklist before you ship
  • Base declares at least one pure virtual (use = 0) and has a virtual destructor.
  • Every concrete derived class overrides all inherited pure virtuals; mark them override and match qualifiers (const, reference, noexcept).
  • No attempts to instantiate the abstract base; construct concrete derived types instead.
  • No object slicing: store/manage via Base&, Base*, or std::unique_ptr<Base> (not by value as Base).
  • No virtual calls (especially pure virtual) in constructors/destructors of the base or derived classes.
  • Avoid default arguments on virtuals (defaults bind statically and can surprise callers).
  • If you declare a pure virtual destructor, provide an out-of-class definition.
  • Consider alternatives (templates/concepts or type erasure) when runtime polymorphism isn’t required.

Want more beginner-friendly tutorials? Visit our C++ hub at CodDesire C++ Tutorials.

Sources / Further reading

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted