Skip to Content
Course content

32: Pure Virtual Functions and Abstract Classes

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When I first started working with polymorphism, I had this nagging feeling that pure virtual functions were just "fancy" virtual functions. I thought that adding = 0 to a function declaration was basically just a note to other programmers saying, "Hey, you should probably override this." I treated them as suggestions rather than requirements.

The "It's Just a Suggestion" Fallacy

If you treat a pure virtual function as a suggestion, you'll eventually run into a runtime disaster. Imagine you're building a payment processing system. You have a base class PaymentMethod and several derived classes like CreditCard and PayPal. If you just use a standard virtual function with an empty body, your code looks like this:

class PaymentMethod {
public:
    virtual void processPayment(double amount) {
        // I'll just leave this empty; the derived classes will handle it.
    }
};

The problem here is that the compiler thinks PaymentMethod is a complete, concrete class. If you (or a teammate) accidentally instantiate PaymentMethod payment; and call processPayment(100.0), the program will run perfectly fine—it will just do absolutely nothing. No error, no crash, and most importantly, no money processed. You've created a "silent failure" bug, which is the hardest kind to track down in a production environment.

Turning Suggestions into Requirements

This is where the pure virtual function comes in. By adding = 0, you aren't just leaving a note; you are changing the fundamental nature of the class. You are telling the C++ compiler: "This class is incomplete. It is conceptually impossible to have a generic 'PaymentMethod' object."

class PaymentMethod {
public:
    // This is now a Pure Virtual Function
    virtual void processPayment(double amount) = 0;
    
    virtual ~PaymentMethod() {} // Always remember your virtual destructor!
};

Now, PaymentMethod has become an Abstract Class. If you try to write PaymentMethod myPayment;, the compiler will stop you dead in your tracks with an error. It won't even let the code compile. This is exactly what we want. We've moved the error from "runtime" (where the customer doesn't get their product) to "compile-time" (where the developer fixes it in five seconds).

Managing the Abstract Interface

You might be wondering, "If I can't create an object of an abstract class, why does the class even exist?" The magic is in the pointers. While you cannot instantiate an abstract class, you can absolutely have a pointer or a reference to one. This is the cornerstone of professional C++ architecture.

I typically use this to create a "plugin" style architecture. Your main logic doesn't need to know if it's dealing with a CreditCard or PayPal object; it only cares that the object adheres to the PaymentMethod contract.

void executeTransaction(PaymentMethod* method, double total) {
    // I don't know WHICH method this is, but I know it MUST have processPayment
    method->processPayment(total);
}

Because processPayment was pure virtual in the base class, C++ guarantees that any concrete class derived from PaymentMethod must have provided its own implementation. If CreditCard forgot to implement it, CreditCard would also become an abstract class, and you wouldn't be able to instantiate it either. It's a foolproof chain of accountability.




📋 Practical Task

Building a Multi-Channel Notification System

Your task is to design a notification system that can send alerts via different channels (Email, SMS, and Push Notifications). To ensure the system is extensible and safe, you must use an abstract base class.

  • Create an abstract base class named NotificationChannel.
  • Define a pure virtual function sendNotification(std::string message) that returns void.
  • Implement two derived classes: EmailChannel and SmsChannel. Each should print a unique message to the console (e.g., "Sending Email: [message]") when sendNotification is called.
  • In your main function, create a std::vector of NotificationChannel* pointers.
  • Add one instance of EmailChannel and one instance of SmsChannel to the vector.
  • Iterate through the vector and call sendNotification on each pointer to demonstrate polymorphism.
  • Crucial: Ensure you include a virtual destructor in the base class to avoid memory leaks when deleting the derived objects via base pointers.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.