C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
32: Pure Virtual Functions and Abstract Classes
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 returnsvoid. - Implement two derived classes:
EmailChannelandSmsChannel. Each should print a unique message to the console (e.g., "Sending Email: [message]") whensendNotificationis called. - In your
mainfunction, create astd::vectorofNotificationChannel*pointers. - Add one instance of
EmailChanneland one instance ofSmsChannelto the vector. - Iterate through the vector and call
sendNotificationon 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.
There are no comments for now.