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.
Driver
│
├── Steering Wheel → Change Direction
├── Accelerator → Increase Speed
└── Brake Pedal → Reduce Speed
Hidden from Driver:
├── Engine Combustion
├── Fuel Injection
├── Transmission
└── Braking MechanicsWhat the User Sees
- Steering wheel.
- Accelerator pedal.
- Brake pedal.
- Gear controls.
What Remains Hidden
- Engine combustion.
- Fuel injection.
- Transmission mechanics.
- Internal braking operations.
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.
User
│
│ sees
▼
Essential Interface
│
│ hides
▼
Complex Implementation| Question | Abstraction |
|---|---|
| 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.
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.
User
│
├── Understand Internal Step 1
├── Understand Internal Step 2
├── Understand Internal Step 3
├── Understand Internal Step 4
└── Finally Perform OperationUser
│
▼
Simple Operation
│
▼
Hidden Internal Implementation
│
▼
ResultReduced 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.
👤 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 Customer | Hidden from Customer |
|---|---|
| Insert card | Card-reading implementation |
| Enter PIN | Authentication process |
| Withdraw money | Balance verification and transaction processing |
| Receive cash | Cash counting mechanism |
| View receipt | Transaction logging and database operations |
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.
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.
Abstract Class
│
├── Defines Common Interface
│
└── Contains Pure Virtual Function
│
▼
Derived Classes Implement
│
┌─────────┴─────────┐
▼ ▼
Implementation A Implementation B| Concept | Purpose |
|---|---|
| Abstract Class | Defines a base abstraction that cannot be instantiated directly |
| Pure Virtual Function | Declares an operation that derived classes may be required to implement |
| Derived Class | Provides a concrete implementation |
| Concrete Class | A class with no unimplemented inherited pure virtual functions |
| Base Pointer/Reference | Allows code to work through the common abstraction |
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.
class ClassName
{
public:
virtual void functionName() = 0;
};class Shape
{
public:
virtual void draw() = 0;
};| Part | Meaning |
|---|---|
| virtual | Makes the function participate in virtual dispatch |
| void | Return type of the function |
| draw | Function name |
| () | Parameter list |
| = 0 | Declares the function as pure virtual |
virtual void draw() = 0;
│ │ │ │
│ │ │ └── Pure Virtual
│ │ └─────── Function Name
│ └──────────── Return Type
└─────────────────── Virtual FunctionA derived class can provide an implementation by declaring a matching function and using the override specifier.
class Circle : public Shape
{
public:
void draw() override
{
std::cout << "Drawing Circle";
}
};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.
#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.
Animal
(Abstract Class)
│
│ sound() = 0
▼
Dog
(Concrete Class)
│
│ sound() implementation
▼
"Dog Barks"Dog BarksAnimal 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.
#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.
Shape
draw() = 0
│
┌───────┴────────┐
▼ ▼
Circle Rectangle
draw() draw()
│ │
▼ ▼
Drawing Circle Drawing RectangleDrawing Circle
Drawing RectangleThe 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.
class Animal
{
public:
virtual void sound() = 0;
};
// ❌ Compilation error
Animal animal;class Dog : public Animal
{
public:
void sound() override
{
std::cout << "Dog Barks";
}
};
// ✅ Concrete object
Dog dog;Animal
│
│ sound() = ?
▼
Incomplete
Cannot Create Object
+
Dog Implementation
│
│ sound() = "Dog Barks"
▼
Complete Concrete Class
Can Create ObjectIf a derived class does not implement all inherited pure virtual functions, that derived class also remains 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 abstractProgram 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 Concrete Object
│
▼
Call Implemented Function
│
▼
Program Ends| Stage | What Happens |
|---|---|
| 1 | The abstract base class defines the common operation |
| 2 | The pure virtual function makes the class abstract |
| 3 | A derived class inherits the abstraction |
| 4 | The derived class implements the required operation |
| 5 | The derived class becomes concrete |
| 6 | A concrete object is created |
| 7 | The implemented function executes |
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.
| Feature | Abstraction | Encapsulation |
|---|---|---|
| Main Focus | Essential behavior and hidden complexity | Bundling and controlled access to state |
| Main Question | What operations should be exposed? | How should internal state be protected? |
| Hides | Unnecessary implementation details | Direct access to internal representation |
| Common C++ Tools | Abstract classes and interfaces | Classes and access specifiers |
| User Sees | Essential operations | Controlled public methods |
| Primary Goal | Reduce conceptual complexity | Protect 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.
class Payment
{
public:
virtual void pay() = 0;
};class BankAccount
{
private:
double balance;
public:
void deposit(double amount)
{
if (amount > 0)
{
balance += amount;
}
}
};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.
Application
│
│ open("data.txt")
▼
File Interface
│
│ hides
▼
Filesystem
│
├── Locate File
├── Check Permissions
├── Access Storage Device
├── Read Disk Blocks
└── Buffer DataUser
│
│ pay()
▼
Payment Interface
│
├── Validate Request
├── Authenticate User
├── Contact Provider
├── Process Transaction
└── Record ResultMost 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.
An abstraction should represent a meaningful concept. Unnecessary abstract classes and excessive layers can make simple code harder to understand.
Common Beginner Mistakes
A class with an unimplemented pure virtual function cannot be instantiated directly.
A derived class remains abstract until all inherited pure virtual requirements are satisfied.
Abstraction focuses on essential behavior and hidden complexity. Encapsulation focuses on bundling and controlling access to internal state.
The = 0 pure-specifier is used with a virtual function declaration.
Without override, signature mistakes can prevent the intended function from implementing the base requirement.
Not every class needs an abstract base. Use abstraction when a meaningful common contract exists.
class Shape
{
public:
virtual void draw() = 0;
};
// ❌ Error
Shape shape;class Circle : public Shape
{
public:
void draw() override
{
std::cout << "Drawing Circle";
}
};
// ✅ Correct
Circle circle;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 abstractclass Computer : public Device
{
public:
void start() override
{
}
void stop() override
{
}
};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.
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.
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.