Abstraction¶
Definition: Abstraction exposes a stable interface and hides implementation details. Users depend on what the type does (the contract), not how it does it (the implementation).
Key Benefits¶
Small public surface with clear semantics
Independent evolution of internals without breaking clients
Fewer ripple effects across codebase
Mock the interface, validate behavior independently
Swap implementations without changing client code
When to Use Abstract Classes¶
Create abstract classes when you want to:
Multiple concrete types must follow the same interface
Different implementations with common behavior
Incomplete or conceptual types shouldn’t be created
Provide base functionality while requiring specialization
Clients provide their own implementations
Define extension points for users
Example Scenarios¶
class Device {
public:
virtual ~Device() = default;
virtual void initialize() = 0;
virtual void read(Buffer& buf) = 0;
virtual void write(const Buffer& buf) = 0;
};
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual void draw() const = 0;
};
class DataSource {
public:
virtual ~DataSource() = default;
virtual bool open() = 0;
virtual Data read() = 0;
virtual bool write(const Data& d) = 0;
virtual void close() = 0;
};
class Widget {
public:
virtual ~Widget() = default;
virtual void render() = 0;
virtual void handle_event(const Event& e) = 0;
virtual void resize(int w, int h) = 0;
};
How is Abstraction Implemented in C++?¶
Define behavior as a contract using pure virtual methods
Enforce implementation in derived types (= 0)
Keep data private/protected; expose intent through methods
Example:
class Vehicle {
public:
virtual ~Vehicle() = default;
// Pure virtual - MUST be implemented in derived classes
virtual void drive() = 0;
// Pure virtual - MUST be implemented in derived classes
virtual void update_location(const Location& location) = 0;
};
Pure virtual Methods (= 0)¶
Definition: A pure virtual method is declared with = 0 and:
Makes the class abstract — cannot be instantiated
Requires derived classes to implement the method
Defines a contract that concrete classes must fulfill
Can optionally provide a default implementation (rare)
Basic Example¶
class Vehicle {
public:
virtual ~Vehicle() = default;
virtual void drive() = 0; // Pure virtual method
};
int main(){
// transportation::Vehicle v; // ERROR: cannot instantiate abstract class
}
class RoboTaxi : public Vehicle {
public:
void drive() override;
/* robo_taxi.cpp
{
// logic
}
*/
};
int main(){
RoboTaxi rt; // OK - RoboTaxi has overridden drive()
}
Note
A caller invokes vehicle->drive() without knowledge of internal details. Any conforming concrete type satisfies the same contract.
Abstract Class Requirements¶
A class is abstract if it has at least one pure virtual method. An abstract class:
✓ Cannot be instantiated directly
✓ Can have constructors (for derived classes to call)
✓ Can have data members (typically protected)
✓ Can mix pure and regular virtual methods
✓ Can have non-virtual methods
class Shape {
protected:
Point center_; // Data member
public:
// Constructor (for derived classes)
Shape(const Point& center) : center_{center} {}
// Pure virtual - must override
virtual double area() const = 0;
virtual void draw() const = 0;
// Regular virtual - can override
virtual void move(const Vector& offset);
/* shape.cpp
{
center_ = center_ + offset;
}*/
// Non-virtual - cannot override
[[nodiscard]] Point get_center() const noexcept{ return center_; }
// Virtual destructor
virtual ~Shape() = default;
};
Pure Virtual with Default Implementation¶
💡 Advanced Pattern: Pure Virtual with Implementation
A pure virtual method can optionally provide a default implementation that derived classes can call explicitly:
class Logger {
public:
virtual ~Logger() = default;
// Pure virtual with implementation
virtual void log(const std::string& message) = 0 {
// Default implementation
std::cout << "[LOG] " << message << '\n';
}
};
class FileLogger : public Logger {
public:
void log(const std::string& message) override;
/* file_logger.cpp
{
// Can call base implementation
Logger::log(message);
// Add file-specific behavior
write_to_file(message);
}*/
};
Note
This pattern is rare but useful when you want to enforce implementation while providing a fallback.
Concrete Classes¶
Definition: A concrete class is a class that can be instantiated. It provides complete implementations for all inherited pure virtual methods.
Key Characteristics¶
Implements all inherited pure virtual methods
Can create objects directly
Provides complete, working functionality
All methods have defined behavior
Example: Complete Hierarchy¶
// Abstract base - defines interface contract
class Vehicle {
public:
virtual ~Vehicle() = default;
virtual void drive() = 0;
virtual void stop() = 0;
};
// Concrete derived class - implements all pure virtuals
class RoboTaxi : public Vehicle {
public:
~RoboTaxi() override = default;
void drive() override;
/* robo_taxi.cpp
{
// RoboTaxi-specific driving logic
}*/
void stop() override;
/* robo_taxi.cpp
{
// RoboTaxi-specific stopping logic
}*/
};
int main(){
RoboTaxi robo_taxi{"ROBOT001", 6}; // OK - fully concrete
robo_taxi.drive(); // OK
}
// Abstract base
class Vehicle {
public:
virtual ~Vehicle() = default;
virtual void drive() = 0;
virtual void stop() = 0;
};
// Still abstract - missing stop() implementation
class Taxi : public Vehicle {
public:
~Taxi() override = default;
void drive() override;
/* taxi.cpp
{
// Taxi-specific driving logic
}*/
// Missing: void stop() override { }
};
int main(){
// ERROR: cannot instantiate abstract class
// Taxi taxi{"TAXI001", 6};
}
Warning
A derived class must implement ALL pure virtual methods from the base class to become concrete. We forgot to implement stop() and therefore, Taxi is still an abstract class.
Design Patterns with Abstraction¶
// Abstract strategy interface
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<int>& data) = 0;
};
// Concrete strategies
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override;
/* quick_sort.cpp
{
// QuickSort implementation
}*/
};
class MergeSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override;
/* merge_sort.cpp
{
// MergeSort implementation
}*/
};
// Client uses abstraction
class DataProcessor {
private:
std::unique_ptr<SortStrategy> strategy_;
public:
void set_strategy(std::unique_ptr<SortStrategy> strategy) {
strategy_ = std::move(strategy);
}
void process(std::vector<int>& data);
/* data_processor.cpp
{
strategy_->sort(data); // Uses abstract interface
}*/
};
// Abstract product
class Document {
public:
virtual ~Document() = default;
virtual void open() = 0;
virtual void save() = 0;
};
// Concrete products
class PDFDocument : public Document {
public:
void open() override;
/* pdf_document.cpp
{
// PDF opening logic
*/}
void save() override;
/* pdf_document.cpp
{
// PDF saving logic
}*/
};
class WordDocument : public Document {
public:
void open() override;
/* word_document.cpp
{
// Word opening logic
}*/
void save() override;
/* word_document.cpp
{
// Word saving logic
}*/
};
// Factory creates objects polymorphically
class DocumentFactory {
public:
static std::unique_ptr<Document> create(const std::string& type) {
if (type == "pdf") {
return std::make_unique<PDFDocument>();
} else if (type == "word") {
return std::make_unique<WordDocument>();
}
return nullptr;
}
};
// Abstract observer interface
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& event) = 0;
};
// Concrete observers
class EmailNotifier : public Observer {
public:
void update(const std::string& event) override;
/* email_notifier.cpp
{
send_email("Event occurred: " + event);
}*/
};
class LoggerObserver : public Observer {
public:
void update(const std::string& event) override;
/* logger_observer.cpp
{
// log_to_file("Event occurred: " + event);
}*/
};
// Subject maintains list of observers
class EventManager {
private:
std::vector<Observer*> observers_;
public:
void attach(Observer* obs);
/* event_manager.cpp
{
observers_.push_back(obs);
}*/
void notify(const std::string& event);
/* event_manager.cpp
{
for (auto* obs : observers_) {
obs->update(event); // Polymorphic call
}
}*/
};
Best Practices¶
Keep interfaces minimal and focused
Show Example
// Good - focused interface
class Drawable {
public:
virtual ~Drawable() = default;
virtual void draw() const = 0;
};
// Avoid - interface too broad
class GraphicsObject {
public:
virtual void draw() const = 0;
virtual void rotate(double angle) = 0;
virtual void scale(double factor) = 0;
virtual void translate(const Vector& offset) = 0;
virtual void set_color(const Color& color) = 0;
// Too many responsibilities!
};
Use abstract classes for contracts, not implementation sharing
If you need shared implementation, consider composition
Abstract classes define “what”, not “how”
Prefer pure virtual over virtual with defaults
Forces derived classes to think about implementation
Avoids silent bugs from forgotten overrides
Always make destructors virtual
Show Example
class AbstractBase {
public:
virtual ~AbstractBase() = default; // Essential!
virtual void do_something() = 0;
};
Many small interfaces beat one large interface (ISP)
Clients shouldn’t depend on methods they don’t use
Document the contract clearly
Show Example
class DataSource {
public:
virtual ~DataSource() = default;
/**
* Reads data from the source.
*
* @return Data buffer, or empty on error
* @throws std::runtime_error if source is not open
*/
virtual std::vector<uint8_t> read() = 0;
};
Common Pitfalls¶
❌ Pitfall 1: Too Many Pure Virtuals
// Avoid - forces too much implementation
class Vehicle {
public:
virtual void start_engine() = 0;
virtual void stop_engine() = 0;
virtual void open_door() = 0;
virtual void close_door() = 0;
virtual void honk_horn() = 0;
virtual void turn_lights_on() = 0;
virtual void turn_lights_off() = 0;
// ... 50 more methods
};
Fix: Break into smaller, focused interfaces (Interface Segregation Principle).
❌ Pitfall 2: Abstract Class with Data Members
// Questionable - mixes interface and implementation
class Shape {
private:
Color color_; // Implementation detail in abstract class?
public:
virtual double area() const = 0;
};
Better: Keep abstract classes focused on interface, use composition for shared data.
❌ Pitfall 3: Forgetting Virtual Destructor
class Base {
public:
// Missing: virtual ~Base() = default;
virtual void foo() = 0;
};
Always add virtual destructor to abstract base classes to prevent resource leaks.
Core Concepts:
Abstraction exposes interface, hides implementation
Abstract classes define contracts using pure
virtualmethodsPure virtual methods (
= 0) must be implemented by derived classesConcrete classes implement all inherited pure virtuals
Always provide virtual destructors in abstract base classes
Keep interfaces minimal and focused
Use abstraction for polymorphic behavior and dependency inversion
Abstract classes cannot be instantiated directly
Derived classes remain abstract until all pure virtuals are implemented
Define what, not how
Enable flexible designs
Hide implementation details