LearnContact
Lesson 2510 min read

Abstraction

You drive a car using the steering wheel and pedals without needing to understand the engine. In this lesson, you will learn how C++ uses abstraction, abstract classes, and pure virtual functions to hide complex implementation details while exposing essential behavior.

Introduction

In the previous lesson, you learned how polymorphism allows a common interface to represent different forms of behavior.

Now imagine that you are driving a car. You use the steering wheel to change direction, the accelerator to increase speed, and the brake pedal to stop.

You do not need to understand how fuel is ignited inside the engine, how the transmission changes gears, or how the braking system creates friction. You only need to know how to use the controls provided to you.

Using a Car
Driver
   │
   ├── Steering Wheel → Change Direction
   ├── Accelerator    → Increase Speed
   └── Brake Pedal    → Reduce Speed

Hidden from Driver:
   ├── Engine Combustion
   ├── Fuel Injection
   ├── Transmission
   └── Braking Mechanics

What the User Sees

  • Steering wheel.
  • Accelerator pedal.
  • Brake pedal.
  • Gear controls.

What Remains Hidden

  • Engine combustion.
  • Fuel injection.
  • Transmission mechanics.
  • Internal braking operations.
User Chooses Operation
Use Simple Interface
Internal System Performs Complex Work
Receive Expected Result
The Main Idea

Abstraction hides unnecessary implementation details and exposes only the essential operations needed to use a system.

What is Abstraction?

Abstraction is the process of hiding implementation details while exposing only the essential features or operations of an object.

In simple terms, abstraction focuses on what an object can do instead of requiring the user to understand exactly how the object performs the work internally.

Abstraction Concept
User
  │
  │ sees
  ▼
Essential Interface
  │
  │ hides
  ▼
Complex Implementation
QuestionAbstraction
What should the user know?Essential operations
What should remain hidden?Unnecessary implementation details
What does the user interact with?A simplified interface
Who handles the internal work?The implementation

WHAT

  • Start the car.
  • Withdraw money.
  • Send a message.
  • Open a file.

HOW

  • How the engine starts.
  • How the bank verifies the transaction.
  • How data travels across the network.
  • How disk sectors are accessed.
Simple Definition

Abstraction shows what an object does while hiding unnecessary details about how it does it.

Why Do We Need Abstraction?

Modern software systems contain thousands or even millions of implementation details. Requiring every user or programmer to understand all of them would make software extremely difficult to use and maintain.

Without Abstraction

  • Users must understand internal details.
  • Complexity spreads throughout the application.
  • Code becomes tightly dependent on implementation.
  • Internal changes can affect many parts of the program.
  • Systems become harder to use and maintain.

With Abstraction

  • Users work with essential operations.
  • Complex details remain inside implementations.
  • Code can depend on clear interfaces.
  • Internal implementations can change more safely.
  • Applications become easier to understand.
Without Abstraction
User
  │
  ├── Understand Internal Step 1
  ├── Understand Internal Step 2
  ├── Understand Internal Step 3
  ├── Understand Internal Step 4
  └── Finally Perform Operation
With Abstraction
User
  │
  ▼
Simple Operation
  │
  ▼
Hidden Internal Implementation
  │
  ▼
Result

Reduced Complexity

Users focus only on the operations they need.

Hidden Details

Implementation decisions remain behind the public interface.

Maintainability

Internal logic can often change without changing how users interact with the system.

Modularity

Different components can expose focused responsibilities.

Reusability

A common abstraction can support multiple implementations.

Scalability

Large systems become easier to organize around clear contracts.

Real-World Analogy

Consider an ATM. A customer interacts with a simple set of visible operations without seeing the complex banking systems working behind the machine.

ATM Abstraction
👤 Customer
     │
     ▼
┌─────────────────────┐
│    ATM Interface    │
│                     │
│  • Insert Card      │
│  • Enter PIN        │
│  • Withdraw Money   │
│  • Check Balance    │
└─────────────────────┘
     │
     │ hides
     ▼
┌─────────────────────┐
│ Internal Operations │
│                     │
│ • Authentication    │
│ • Account Lookup    │
│ • Balance Check     │
│ • Cash Counting     │
│ • Transaction Log   │
└─────────────────────┘
Visible to CustomerHidden from Customer
Insert cardCard-reading implementation
Enter PINAuthentication process
Withdraw moneyBalance verification and transaction processing
Receive cashCash counting mechanism
View receiptTransaction logging and database operations
Insert Card
Enter PIN
Choose Withdraw
Hidden Processing
Receive Cash
Interface vs Implementation

The customer uses the ATM interface. The banking and machine implementation remains hidden behind that interface.

Characteristics of Abstraction

Abstraction is based on separating essential behavior from unnecessary implementation details.

Hides Implementation

Internal working details are not required by the user of the abstraction.

Shows Essentials

Only meaningful operations are exposed through the interface.

Reduces Complexity

Users work with a smaller and simpler conceptual model.

Separates Responsibilities

The interface describes expected behavior while implementations perform the work.

Supports Multiple Implementations

Different classes can implement the same abstract operation differently.

Encourages Modular Design

Systems can be divided into components with clear responsibilities.

Key Characteristics

  • Hides unnecessary implementation details.
  • Exposes essential operations.
  • Focuses on what should be done.
  • Reduces complexity for users of the class.
  • Separates interface from implementation.
  • Supports multiple concrete implementations.
  • Works closely with inheritance and polymorphism.
  • Encourages modular software design.
Abstraction Does Not Mean Hiding Everything

A useful abstraction exposes enough information to perform meaningful operations while hiding details that users do not need.

Abstraction in C++

C++ can express abstraction in several ways, including functions, classes, access control, and carefully designed interfaces. In object-oriented programming, abstract classes and pure virtual functions are commonly used to define abstract interfaces.

1️⃣ Abstract Classes

Classes that cannot be instantiated directly and are commonly used as base interfaces.

2️⃣ Pure Virtual Functions

Virtual functions declared with = 0 that make a class abstract.

Abstraction Structure
Abstract Class
      │
      ├── Defines Common Interface
      │
      └── Contains Pure Virtual Function
                    │
                    ▼
          Derived Classes Implement
                    │
          ┌─────────┴─────────┐
          ▼                   ▼
   Implementation A    Implementation B
ConceptPurpose
Abstract ClassDefines a base abstraction that cannot be instantiated directly
Pure Virtual FunctionDeclares an operation that derived classes may be required to implement
Derived ClassProvides a concrete implementation
Concrete ClassA class with no unimplemented inherited pure virtual functions
Base Pointer/ReferenceAllows code to work through the common abstraction
Important Rule

A class becomes abstract if it has at least one pure virtual function that remains unimplemented for that class.

General Syntax

A pure virtual function is declared by writing = 0 in its declaration.

Pure Virtual Function Syntax
class ClassName
{
public:
    virtual void functionName() = 0;
};
Example
class Shape
{
public:
    virtual void draw() = 0;
};
PartMeaning
virtualMakes the function participate in virtual dispatch
voidReturn type of the function
drawFunction name
()Parameter list
= 0Declares the function as pure virtual
Reading the Declaration
virtual void draw() = 0;
   │      │    │    │
   │      │    │    └── Pure Virtual
   │      │    └─────── Function Name
   │      └──────────── Return Type
   └─────────────────── Virtual Function

A derived class can provide an implementation by declaring a matching function and using the override specifier.

Derived Implementation
class Circle : public Shape
{
public:
    void draw() override
    {
        std::cout << "Drawing Circle";
    }
};
Use override

Use override whenever a derived function is intended to implement or override a virtual function from the base class.

Example 1: Abstract Class

The following program creates an abstract Animal class containing the pure virtual function sound(). The Dog class provides the required implementation.

Example 1: Abstract Class
#include <iostream>

class Animal
{
public:
    virtual void sound() = 0;
};

class Dog : public Animal
{
public:
    void sound() override
    {
        std::cout << "Dog Barks";
    }
};

int main()
{
    Dog dog;
    dog.sound();

    return 0;
}

How the Program Works

  • Animal declares the pure virtual function sound().
  • Because Animal contains a pure virtual function, Animal is abstract.
  • Dog publicly inherits from Animal.
  • Dog implements sound().
  • Dog therefore becomes a concrete class.
  • A Dog object named dog is created.
  • dog.sound() executes the Dog implementation.
Define Animal Abstraction
Declare Pure Virtual sound()
Create Dog Class
Implement sound()
Create Dog Object
Call sound()
Class Relationship
Animal
(Abstract Class)
     │
     │ sound() = 0
     ▼
    Dog
(Concrete Class)
     │
     │ sound() implementation
     ▼
 "Dog Barks"
Output
Dog Barks
Why Animal Is Abstract

Animal contains a pure virtual function. Therefore, Animal cannot be instantiated directly.

Example 2: Multiple Derived Classes

One of the main strengths of abstraction is that several derived classes can follow the same common interface while providing different implementations.

Example 2: Multiple Derived Classes
#include <iostream>

class Shape
{
public:
    virtual void draw() = 0;
};

class Circle : public Shape
{
public:
    void draw() override
    {
        std::cout << "Drawing Circle"
                  << std::endl;
    }
};

class Rectangle : public Shape
{
public:
    void draw() override
    {
        std::cout << "Drawing Rectangle";
    }
};

int main()
{
    Circle circle;
    Rectangle rectangle;

    circle.draw();
    rectangle.draw();

    return 0;
}

How the Program Works

  • Shape defines the common draw() abstraction.
  • Shape is abstract because draw() is pure virtual.
  • Circle inherits from Shape and implements draw().
  • Rectangle inherits from Shape and implements draw().
  • Both derived classes follow the same interface.
  • Each derived class provides its own behavior.
One Interface, Multiple Implementations
              Shape
         draw() = 0
               │
       ┌───────┴────────┐
       ▼                ▼
    Circle          Rectangle
    draw()            draw()
       │                │
       ▼                ▼
Drawing Circle   Drawing Rectangle
Define Shape Interface
Create Derived Classes
Implement draw()
Create Concrete Objects
Call Implementations
Output
Drawing Circle
Drawing Rectangle
Common Contract

The abstract class defines the operation that derived classes share. Each concrete class decides how that operation is performed.

Why Can’t We Create Objects of an Abstract Class?

An abstract class represents an incomplete abstraction. At least one required operation does not have a concrete implementation for objects of that class.

Abstract Class Object

  • Contains an unresolved pure virtual operation.
  • Does not provide complete concrete behavior.
  • Cannot be instantiated directly.

Concrete Derived Object

  • Provides required implementations.
  • Has complete concrete behavior.
  • Can be instantiated.
Incorrect
class Animal
{
public:
    virtual void sound() = 0;
};

// ❌ Compilation error
Animal animal;
Correct
class Dog : public Animal
{
public:
    void sound() override
    {
        std::cout << "Dog Barks";
    }
};

// ✅ Concrete object
Dog dog;
Abstract to Concrete
Animal
   │
   │ sound() = ?
   ▼
Incomplete
Cannot Create Object

        +

Dog Implementation
   │
   │ sound() = "Dog Barks"
   ▼
Complete Concrete Class
Can Create Object
Derived Classes Can Also Remain Abstract

If a derived class does not implement all inherited pure virtual functions, that derived class also remains abstract.

Derived Class Still Abstract
class Device
{
public:
    virtual void start() = 0;
    virtual void stop() = 0;
};

class Machine : public Device
{
public:
    void start() override
    {
    }

    // stop() is not implemented
};

// Machine is still abstract

Program Execution Flow

The complete abstraction process begins with defining a common abstract interface and ends with creating and using a concrete derived object.

Program Starts
Define Abstract Class
Declare Pure Virtual Function
Create Derived Class
Implement Required Function
Create Derived Object
Call Function
Program Ends
Execution Flow
Program Starts
      │
      ▼
Define Abstract Class
      │
      ▼
Declare Pure Virtual Function
      │
      ▼
Create Derived Class
      │
      ▼
Implement Required Function
      │
      ▼
Create Concrete Object
      │
      ▼
Call Implemented Function
      │
      ▼
Program Ends
StageWhat Happens
1The abstract base class defines the common operation
2The pure virtual function makes the class abstract
3A derived class inherits the abstraction
4The derived class implements the required operation
5The derived class becomes concrete
6A concrete object is created
7The implemented function executes
Design Flow

The abstract class defines the required behavior. The concrete derived class provides the actual implementation.

Abstraction vs Encapsulation

Abstraction and encapsulation are closely related object-oriented concepts, but they focus on different design problems.

FeatureAbstractionEncapsulation
Main FocusEssential behavior and hidden complexityBundling and controlled access to state
Main QuestionWhat operations should be exposed?How should internal state be protected?
HidesUnnecessary implementation detailsDirect access to internal representation
Common C++ ToolsAbstract classes and interfacesClasses and access specifiers
User SeesEssential operationsControlled public methods
Primary GoalReduce conceptual complexityProtect invariants and control access

Abstraction

  • Focuses on essential behavior.
  • Hides unnecessary complexity.
  • Defines what operations are available.
  • Often expressed through abstract interfaces.

Encapsulation

  • Groups data and behavior.
  • Controls access to internal state.
  • Protects class invariants.
  • Uses access specifiers such as private.
Abstraction Example
class Payment
{
public:
    virtual void pay() = 0;
};
Encapsulation Example
class BankAccount
{
private:
    double balance;

public:
    void deposit(double amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
    }
};
They Often Work Together

A well-designed class can use abstraction to expose essential operations and encapsulation to protect its internal state and implementation.

Real-World Applications

Abstraction is used throughout software development whenever complex systems expose simple operations to users or other components.

Banking

withdraw() and deposit() hide authentication, database updates, logging, and transaction processing.

Mobile Phones

Users tap Call without managing radio hardware, network protocols, or signal processing.

Operating Systems

Opening a file hides disk sectors, device drivers, buffering, and filesystem operations.

Game Development

attack() and jump() can hide animation, physics, collision, and damage calculations.

Payment Systems

pay() can hide provider communication, validation, authentication, and transaction processing.

Cloud Services

Simple APIs can hide distributed infrastructure, storage, networking, and scaling logic.

File System Example
Application
    │
    │ open("data.txt")
    ▼
File Interface
    │
    │ hides
    ▼
Filesystem
    │
    ├── Locate File
    ├── Check Permissions
    ├── Access Storage Device
    ├── Read Disk Blocks
    └── Buffer Data
Payment Example
User
  │
  │ pay()
  ▼
Payment Interface
  │
  ├── Validate Request
  ├── Authenticate User
  ├── Contact Provider
  ├── Process Transaction
  └── Record Result
Everyday Software Depends on Abstraction

Most useful software would be impossible to manage if every user had to understand every internal operation.

Advantages of Abstraction

Reduced Complexity

Users interact with essential operations instead of internal details.

Easier Maintenance

Implementation details can often change behind a stable interface.

Multiple Implementations

Different concrete classes can follow the same abstraction.

Modular Design

Components can be organized around focused responsibilities.

Reduced Dependency

Calling code can depend on an abstraction instead of a specific implementation.

Better Scalability

Clear interfaces help large applications remain understandable.

  • Reduces complexity for users of a class or component.
  • Exposes only essential operations.
  • Separates interface from implementation.
  • Allows multiple implementations behind a common contract.
  • Can reduce dependency on concrete classes.
  • Improves modularity.
  • Makes many systems easier to extend.
  • Supports maintainable application architecture.
Poor Abstractions Can Increase Complexity

An abstraction should represent a meaningful concept. Unnecessary abstract classes and excessive layers can make simple code harder to understand.

Common Beginner Mistakes

Creating an Object of an Abstract Class

A class with an unimplemented pure virtual function cannot be instantiated directly.

Forgetting to Implement Every Pure Virtual Function

A derived class remains abstract until all inherited pure virtual requirements are satisfied.

Confusing Abstraction with Encapsulation

Abstraction focuses on essential behavior and hidden complexity. Encapsulation focuses on bundling and controlling access to internal state.

Forgetting virtual

The = 0 pure-specifier is used with a virtual function declaration.

Forgetting override

Without override, signature mistakes can prevent the intended function from implementing the base requirement.

Creating Unnecessary Abstract Classes

Not every class needs an abstract base. Use abstraction when a meaningful common contract exists.

Creating an Abstract Object
class Shape
{
public:
    virtual void draw() = 0;
};

// ❌ Error
Shape shape;
Correct Concrete Object
class Circle : public Shape
{
public:
    void draw() override
    {
        std::cout << "Drawing Circle";
    }
};

// ✅ Correct
Circle circle;
Incomplete Derived Class
class Device
{
public:
    virtual void start() = 0;
    virtual void stop() = 0;
};

class Computer : public Device
{
public:
    void start() override
    {
    }

    // ❌ stop() is missing
};

// Computer is still abstract
Complete Derived Class
class Computer : public Device
{
public:
    void start() override
    {
    }

    void stop() override
    {
    }
};
Check Every Required Operation

When a derived class unexpectedly remains abstract, verify that every inherited pure virtual function has been implemented with the correct signature.

Best Practices

  • Use abstract classes when related classes genuinely share a common behavioral contract.
  • Keep abstract interfaces focused on essential responsibilities.
  • Avoid adding unrelated operations to the same abstraction.
  • Use meaningful names for abstract classes and operations.
  • Use override for every intended implementation of a virtual function.
  • Give polymorphic base classes appropriate virtual destructors.
  • Depend on abstractions when multiple implementations must be interchangeable.
  • Keep implementation-specific details inside concrete classes.
  • Do not expose internal details that users do not need.
  • Avoid creating abstraction layers without a clear design benefit.
  • Keep interfaces stable when possible.
  • Separate unrelated responsibilities into different abstractions.
Focused Abstract Interface
class Shape
{
public:
    virtual ~Shape() = default;

    virtual void draw() = 0;
};

Poor Abstraction

  • Contains unrelated responsibilities.
  • Exposes implementation-specific details.
  • Changes frequently.
  • Forces derived classes to implement irrelevant operations.

Good Abstraction

  • Represents one meaningful concept.
  • Exposes essential behavior.
  • Hides unnecessary details.
  • Provides a clear contract for implementations.
Keep the Contract Clear

A good abstraction tells users what operations are available without forcing them to understand the internal implementation.

Frequently Asked Questions

What is abstraction?

Abstraction is the process of exposing essential behavior while hiding unnecessary implementation details.

What does abstraction focus on?

It focuses on what operations are available rather than requiring users to understand every internal implementation detail.

What is an abstract class?

An abstract class is a class that cannot be instantiated directly because it has at least one pure virtual function that remains unimplemented for that class.

What is a pure virtual function?

A pure virtual function is a virtual function declared with the = 0 pure-specifier.

How is a pure virtual function declared?

For example: virtual void draw() = 0;

Can we create an object of an abstract class?

No. Abstract classes cannot be instantiated directly.

Can we create a pointer to an abstract class?

Yes. A pointer or reference to an abstract base class can refer to an appropriate derived object.

When does a derived class become concrete?

A derived class becomes concrete when no pure virtual functions remain unimplemented for that class.

What happens if a derived class does not implement every required pure virtual function?

The derived class also remains abstract.

What is the difference between abstraction and encapsulation?

Abstraction focuses on essential behavior and hiding unnecessary complexity, while encapsulation groups data and behavior and controls access to internal state.

Does every abstract class need many pure virtual functions?

No. A single pure virtual function is enough to make a class abstract.

Why should we use override?

The override specifier allows the compiler to verify that a derived function correctly overrides a virtual base function.

Can an abstract class contain normal functions?

Yes. An abstract class can contain normal member functions, data members, constructors, and implemented virtual functions.

Can an abstract class have a constructor?

Yes. Its constructor can be used when constructing the base portion of a derived object.

Why is abstraction important?

It reduces complexity, separates interfaces from implementations, and helps organize flexible and maintainable software.

Key Takeaways

  • Abstraction exposes essential behavior while hiding unnecessary implementation details.
  • It focuses on what operations are available rather than every internal step.
  • Abstraction reduces complexity for users of a system.
  • Abstract classes are commonly used to define object-oriented abstractions in C++.
  • An abstract class cannot be instantiated directly.
  • A pure virtual function is declared using the = 0 pure-specifier.
  • A class with an unimplemented pure virtual function is abstract.
  • Derived classes can provide implementations of inherited pure virtual functions.
  • A derived class remains abstract if required pure virtual functions remain unimplemented.
  • A concrete class can be instantiated.
  • The override specifier helps verify derived implementations.
  • Multiple derived classes can implement the same abstract operation differently.
  • Abstraction works closely with inheritance and polymorphism.
  • Abstract base pointers and references can refer to concrete derived objects.
  • Abstraction and encapsulation are related but different concepts.
  • Abstraction focuses on essential behavior and complexity.
  • Encapsulation focuses on bundling and controlled access to internal state.
  • Good abstractions expose focused and meaningful contracts.
  • Unnecessary abstraction layers can increase complexity.
  • Abstraction is a fundamental principle of object-oriented software design.

Summary

Abstraction is the process of exposing essential operations while hiding unnecessary implementation details.

In C++, abstract classes and pure virtual functions are commonly used to define behavioral contracts. A pure virtual function is declared using = 0, and a class with an unimplemented pure virtual function cannot be instantiated directly.

Derived classes provide concrete implementations of the required operations. Multiple derived classes can follow the same abstraction while performing the work differently.

Abstraction reduces complexity by separating what a component provides from how the component performs its internal work.

Although abstraction and encapsulation often work together, abstraction focuses on essential behavior and hidden complexity, while encapsulation focuses on bundling state and behavior and controlling access to internal representation.

By designing focused abstractions, you can build applications that are easier to understand, maintain, extend, and adapt to multiple implementations.

With the major object-oriented programming concepts now covered, the next lesson introduces templates, which allow C++ code to work generically with different data types.

Next Lesson →

Templates