A regular virtual function says "derived classes may override me." A pure virtual function says "derived classes must override me, and the base class cannot be instantiated until they do." That single change turns a class from a thing you can create into a contract that other classes have to fulfill. This lesson covers the syntax, what changes about the class once you add the = 0 marker, and the edge cases.
= 0 SyntaxA pure virtual function is declared by writing = 0 after the parameter list of a virtual member function.
The = 0 at the end is called the pure specifier. It does not assign zero to anything. It is not setting a function pointer to null. It is a piece of declaration syntax that means "this function has no default implementation that the class wants to commit to, and any concrete derived class must supply one."
The choice of = 0 was historical. The committee needed a token that already existed in the grammar and wouldn't break old code. They picked the digit zero because it was unused in that position and was easy to type. Don't read meaning into the value itself. "The address of this function is zero" is wrong. "A marker that completes the declaration" is right.
A class with at least one pure virtual function is called an abstract class. Variables of pointer or reference type to an abstract class can be declared, but an instance cannot be created.
Uncommenting the line produces an error like:
The compiler refuses to create a Notification because it doesn't know what send should do. The function exists in the type's interface but has no body to call. Constructing such an object would leave a member function in an undefined state, so the language blocks it at compile time.
Once a class is abstract, the rule is simple. A derived class becomes concrete (creatable) only when every pure virtual function inherited from its bases has a non-pure override in that class or in some ancestor below the one that declared it pure.
If a derived class overrides every pure virtual, it is concrete. If it misses even one, it is still abstract and cannot be instantiated either.
EmailNotification overrides both pure virtuals, so it is concrete and the local variable is fine.
Consider a derived class that only overrides one of the two:
The commented line fails to compile with the same "cannot declare variable to be of abstract type" message, except this time the unimplemented function is format. SmsNotification inherits format from Notification, where it is pure. Since SmsNotification doesn't override format, it inherits the pure specifier along with the declaration. The compiler treats SmsNotification as abstract too, and refuses to construct it.
This is a common cause of "I made a class but it says it's abstract." The fix is to find the missing override.
The diagram captures the rule visually. Inheriting from an abstract class isn't enough to become concrete. A class only escapes the "abstract" label by supplying a real body for every pure virtual.
The "cannot instantiate" rule applies to objects, not to pointers or references. Notification* or Notification& are perfectly legal, and they are how polymorphic code talks to the abstract base.
notifyCustomer takes a Notification&. The reference is bound to a concrete derived object at the call site. There is never an actual Notification instance anywhere in memory. The reference is a way to refer to "any concrete subclass of Notification" without committing to which one. Virtual dispatch on channel.send(message) finds the right send based on the actual object behind the reference.
The same idea works with pointers and with smart pointers:
A std::vector<std::unique_ptr<Notification>> is a legal container of pointers to an abstract type. Each slot owns a concrete subclass, and the loop talks to all of them through the base type. This is what abstract classes are for in practice: writing code against a contract instead of a specific implementation.
The = 0 says "every concrete derived class must provide its own override." It does not say "the base class cannot have an implementation." A pure virtual function can have a body. The body isn't picked up automatically; derived classes have to opt into it.
The body is provided outside the class, with the regular member function definition syntax:
Notification::send is declared pure (= 0) and also defined out of line. EmailNotification overrides send so that it is concrete, and the override explicitly calls Notification::send(message) to reuse the base behavior before adding its own. The qualified call is the only way to reach the base body, because virtual dispatch through the type itself would just call back into the override.
This pattern fits when the base has some shared logic that every subclass will probably want, but no subclass would be correct using only that logic. The pure specifier forces every concrete subclass to think about it ("send must be written, even if it only forwards to the base"), while the body offers a shortcut for the common case.
A small caveat. The body for a pure virtual function is defined out of line in most code, like the example above. C++ allows defining it inline inside the class with virtual void send(...) = 0 { ... } on some compilers, but this is non-portable. Stick to the out-of-line form.
A destructor can be pure virtual too. This is a special case because of how destructor calls work: when a derived object is destroyed, the derived destructor runs first, then control automatically passes to the base destructor, regardless of whether the base is virtual, pure, or neither. The base destructor body must exist, because the chain will call it.
That leads to syntax that looks contradictory at first.
The destructor is declared with = 0 inside the class, and then defined right below with Notification::~Notification() { ... }. Without the body, the linker would complain. Forgetting the body produces this error:
The error happens at link time, not compile time, because the destructor declaration is fine. The trouble shows up when EmailNotification's destructor tries to chain into the base's destructor and can't find it.
Why use a pure virtual destructor? The pattern fits when a class should be abstract but there's no other natural pure virtual function to put there. The class has a few virtuals with sensible default bodies, no abstract operations of its own, and direct instantiation should still be forbidden. Marking the destructor = 0 is the lightest way to make the class abstract. The destructor still runs as expected because the body is supplied.
The diagram captures the split: the = 0 makes the class abstract, but the body is still mandatory because the destructor chain will call it.
Most pure virtual functions never get called through a base. Concrete subclasses provide their own bodies, and virtual dispatch finds the override. The pure version exists as a slot in the vtable that says "concrete subclasses fill me in."
But the slot has to point somewhere. Compilers (Itanium ABI, used by g++ and clang++) fill it with a sentinel function called __cxa_pure_virtual. If that function ever gets called, it prints a message and aborts the program.
When could a pure virtual call actually happen? It shouldn't, because the language won't let you instantiate an abstract class directly. But there is one corner: during the construction or destruction of an abstract base, the object's vtable temporarily points at the base's own vtable, not the derived one. Any virtual call made from inside the base constructor or destructor body resolves to the base's version.
For a regular virtual that has a body, that's a "the override won't be called yet" surprise. For a pure virtual with no body in the base, that same surprise becomes a runtime crash, because the call lands on __cxa_pure_virtual.
Running this aborts with the "pure virtual method called" message above. The reason matches that lesson's discussion of calling virtual functions from constructors. Inside Notification::Notification, the object is still under construction as a Notification. The vptr points at Notification's vtable, where send is the pure slot. The call goes through that slot, which is __cxa_pure_virtual, and the runtime terminates the program.
The fix is the same: don't call virtual functions from constructors or destructors of a base class. With a pure virtual, doing so guarantees a crash. With a regular virtual, it works but ignores derived overrides. Either way, run derived behavior from a separate init() call that runs after construction finishes.
A pure virtual function uses a vtable slot like any other virtual function. There's no runtime overhead difference between calling a pure virtual override and a regular virtual override after the object is constructed. The only behavioral wrinkle is the sentinel slot's contents while the object is being built.
A common shape in modern C++ is an abstract class that has no data, only pure virtual functions, plus a virtual destructor. That shape is C++'s closest thing to what some other languages call an interface. It is a class, structurally, but it does the same job: it defines a set of operations that subclasses must implement, without supplying any state or behavior of its own.
PaymentMethod has nothing in it except a pure virtual charge and a virtual destructor. There are no fields, no concrete logic, no helper methods. Subclasses implement charge however they need. checkout works against PaymentMethod& and doesn't care which concrete subclass it gets. New payment methods can be added later without touching checkout.
This barely scratches the surface of the topic. The Abstraction section covers the design discussion properly, including when "interface-like" classes fit best and how they relate to dependency inversion. For now, the takeaway is that a pure-virtual-only class is a legal, common, and useful shape in C++ code.
10 quizzes