Pure Virtual Function with Definition in C++

In C++, declaring a member function as pure virtual typically denotes an abstract interface, requiring derived classes to provide their own implementations. However, the C++ language standard explicitly allows pure virtual functions to contain an actual definition. This article explores how defining a pure virtual function works, where its definition must reside, and the primary design patterns that utilize this capability, such as pure virtual destructors and mandatory overrides with reusable base logic.

Abstract Status Remains Unchanged

Declaring a function with = 0 defines the containing class as an abstract class. Providing an implementation for that function does not alter this behavior:

class Base {
public:
    virtual void process() = 0;
};

void Base::process() {
    // Valid implementation
}

Even though Base::process() has a body, Base cannot be instantiated. Any attempt to create an object of type Base directly will result in a compilation error.

Location of the Definition

According to the C++ standard, a pure virtual function cannot define its body inline inside the class declaration. The pure specifier (= 0) and the function body cannot share the same declaration:

// Ill-formed (syntax error)
class Base {
public:
    virtual void process() = 0 { } 
};

// Well-formed
class Base {
public:
    virtual void process() = 0;
};

void Base::process() {
    // Implementation outside the class body
}

The function body must be placed outside the class declaration, either in the same header file (often marked inline) or within a corresponding source (.cpp) file.

Overriding and Explicit Invocation

Derived classes do not inherit the implementation as a default implementation in the virtual dispatch table. To make a derived class instantiable, it must still explicitly override the pure virtual function. If the derived class does not override it, the derived class also remains abstract.

When an override is provided, the derived class can explicitly invoke the base class implementation using qualified name lookup:

class Derived : public Base {
public:
    void process() override {
        // Explicitly calling the base implementation
        Base::process();
        
        // Additional derived-specific logic
    }
};

This pattern is useful when an interface designer wants to force derived class authors to think about their implementation (preventing accidental inheritance of a default behavior), while still providing reusable core logic that the derived class can opt into calling.

Pure Virtual Destructors

The most critical use case for defining a pure virtual function is the pure virtual destructor. When a designer wants to create an abstract class that has no other natural candidate for a pure virtual function, the destructor can be marked pure virtual:

class AbstractBase {
public:
    virtual ~AbstractBase() = 0;
};

Unlike regular virtual member functions, base class destructors are always called implicitly when a derived class object is destroyed. If AbstractBase does not provide an implementation for its pure virtual destructor, the code will compile, but the linker will fail with an undefined reference error as soon as any derived class is instantiated and destroyed. Therefore, any pure virtual destructor must have a definition:

AbstractBase::~AbstractBase() {
    // Cleanup code, or simply empty
}