C++ / a working model

26 / 163   ·   C++11   ·   8 min

Abstract class and pure virtual function definitions

Keep this sentence

An abstract class cannot create a complete object, but it can have state, constructors, and ordinary implementations. Pure virtual means a concrete derived class must provide a non-pure final overrider; it does not mean the function cannot have a definition. A pure virtual destructor still needs an available definition when a derived object is actually destroyed.

In this lesson
  1. Abstractness looks at the final overrider
  2. A pure virtual function may still provide a reusable definition
  3. Do not invoke an unfinished derived layer from the construction phase
  4. Example
  5. Exercise

Abstractness looks at the final overrider

The = 0 at the end of a virtual function declaration is a pure-specifier, not a way to set a function pointer to null. As long as a class still has a virtual function whose final overrider is pure virtual, it is an abstract class and cannot directly construct a complete object. Pointers, references, and the base-class subobject inside a derived object can still use this type.

An abstract class may have constructors, data members, and ordinary member functions, used to establish shared state or a fixed procedure. A derived class can also redeclare an already-implemented virtual function as pure virtual, expressing that a more specific layer has not yet completed the requirement. Whether the class can ultimately be instantiated cannot be decided solely by whether the current class writes = 0.

A pure virtual function may still provide a reusable definition

A pure virtual function may be defined outside the class, but the class cannot write both a pure-specifier and a function body on the same in-class declaration. A derived class can call this implementation through an explicitly qualified Base::f(), then add its own processing. That qualified call is not dynamically dispatched, so it will not recurse back into the derived overrider.

Having a definition does not cancel the pure-virtual property. In the example, Job::cost returns a shared base cost, but Job remains an abstract type and ConcreteJob must provide its own override. A pure virtual destructor likewise needs a definition, because destroying a derived object necessarily continues by destroying the base-class subobject; the base destructor cannot be absent.

Do not invoke an unfinished derived layer from the construction phase

An abstract-class constructor may call ordinary member functions and may perform legitimate base-class-phase work, but it must not make a pure virtual call, directly or indirectly, on the object under construction. The same restriction applies during destruction. This is undefined behavior; writing an out-of-class definition for the pure virtual function does not automatically make it safe.

If you need shared initialization logic, extract a non-virtual helper and call it explicitly. When you need derived behavior, wait until the object is fully constructed and then call through the interface. The example calls cost only after a complete ConcreteJob already exists, both to demonstrate the interface constraint and to avoid mixing construction order with a runtime extension point.

Pitfalls

  • A definition for a pure virtual function does not automatically discharge a derived class's implementation duty; if the final overrider is still pure virtual, the derived class remains abstract.
  • The definition of a pure virtual destructor is not optional decoration; creating and destroying a derived object will need it, and omitting it often appears as a linker error.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>
#include <type_traits>

class Job {
public:
    virtual int cost() const = 0;
    virtual ~Job() = 0;
};

int Job::cost() const { return 5; }
Job::~Job() = default;

class ConcreteJob final : public Job {
public:
    int cost() const override { return Job::cost() + 3; }
};

int main() {
    static_assert(std::is_abstract<Job>::value, "abstract interface");
    static_assert(!std::is_abstract<ConcreteJob>::value, "complete implementation");
    ConcreteJob job;
    const Job& interface = job;
    assert(interface.cost() == 8);
    assert(job.Job::cost() == 5);
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread objects-abstract.cpp -o example && ./example

Expected result

Expected: exit 0, no output; every assert holds.

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

If you delete only the override definition of ConcreteJob::cost and keep the out-of-class definition of Job::cost, can ConcreteJob be instantiated?

Show a reference answer

No. The final overrider of cost inherited by ConcreteJob is still the pure virtual Job::cost. An out-of-class function body only permits explicitly qualified reuse; it does not change the pure-specifier. You must restore a non-pure override. It may simply write return Job::cost(); but that derived declaration itself is still required.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog