Abstraction in C++
Abstraction in C++ Interview with follow-up questions
1. What is abstraction in C++ and why is it important?
Abstraction is the principle of exposing only the essential characteristics of an entity while hiding irrelevant implementation details. In C++, it means defining what an object does (its interface) separately from how it does it (its implementation).
Why it matters
- Manages complexity — a programmer using a
std::vectordoes not need to know about its heap allocation strategy or growth factor; they just callpush_back. - Reduces coupling — callers depend on a stable interface, not on internal details that may change.
- Enables substitutability — different implementations of the same abstract interface can be swapped in without changing callers (Liskov Substitution Principle).
- Improves readability — code written in terms of domain concepts (
Shape,Stream,Container) is easier to reason about than code tangled with implementation details.
Two levels of abstraction in C++
| Level | Mechanism |
|---|---|
| Data abstraction | Classes with private data and public member functions |
| Procedural abstraction | Functions, templates, and abstract base classes that hide algorithmic detail |
Example
// Abstract interface — what a sorter does, not how
class Sorter {
public:
virtual void sort(std::vector& data) = 0;
virtual ~Sorter() = default;
};
// Concrete implementation — hidden behind the interface
class QuickSorter : public Sorter {
public:
void sort(std::vector& data) override {
std::sort(data.begin(), data.end()); // implementation detail
}
};
Callers hold a Sorter* and call sort(); they never see QuickSorter internals.
C++20 and beyond
Concepts provide compile-time abstraction: you describe the requirements a type must satisfy (Sortable, Drawable) without mandating a class hierarchy. This is abstraction without inheritance overhead.
Follow-up 1
How does abstraction improve code readability and maintainability?
Abstraction improves code readability and maintainability by providing a high-level view of the system or objects being modeled. By hiding unnecessary details, abstraction allows developers to focus on the essential features and functionality. This makes the code easier to understand and maintain. Additionally, abstraction promotes code reusability, as the simplified models can be used in different contexts without worrying about the underlying implementation details. This reduces code duplication and improves overall code quality.
Follow-up 2
Can you give an example of abstraction?
Sure! Let's consider a class called 'Car' in C++. The 'Car' class can have attributes like 'color', 'brand', and 'model', and methods like 'start()', 'accelerate()', and 'stop()'. From the perspective of a user of the 'Car' class, they don't need to know the internal implementation details of how the 'start()', 'accelerate()', and 'stop()' methods work. They only need to know how to use these methods to interact with the 'Car' object. This is an example of abstraction, where the complex details of the 'Car' class are hidden, and only the essential features are exposed to the user.
Follow-up 3
How does abstraction contribute to the concept of data hiding?
Abstraction and data hiding are closely related concepts. Abstraction involves hiding unnecessary details and exposing only the essential features of an object or a system. Data hiding, on the other hand, is a technique used to restrict access to certain data members of a class. By combining abstraction and data hiding, we can create classes with well-defined interfaces, where the internal implementation details are hidden from the user. This helps in achieving better encapsulation and reduces the chances of accidental misuse or modification of the internal data.
Follow-up 4
What is the difference between abstraction and encapsulation?
Abstraction and encapsulation are related concepts in object-oriented programming, but they have different focuses. Abstraction is about representing complex real-world entities as simplified models, hiding unnecessary details and exposing only the essential features. Encapsulation, on the other hand, is about bundling data and methods together into a single unit (class) and controlling access to that unit. Encapsulation helps in achieving data hiding and information hiding, while abstraction helps in managing complexity and improving code reusability.
2. How is abstraction implemented in C++?
Abstraction in C++ is implemented through several complementary mechanisms:
1. Abstract classes and pure virtual functions
A class with at least one pure virtual function (= 0) cannot be instantiated and acts as an interface contract. Derived classes must provide implementations.
class Stream {
public:
virtual void write(std::span data) = 0;
virtual size_t read(std::span buf) = 0;
virtual ~Stream() = default;
};
class FileStream : public Stream {
public:
void write(std::span data) override { /* file I/O */ }
size_t read(std::span buf) override { /* file I/O */ }
};
Callers program to Stream& — the concrete FileStream, NetworkStream, or MemoryStream is an implementation detail.
2. Concrete classes with private internals (data abstraction)
Even a non-abstract class abstracts its implementation by keeping data private and exposing only a public interface:
class Timer {
public:
void start();
void stop();
double elapsed_ms() const;
private:
// implementation detail: platform clock handle, start timestamp, etc.
};
3. Templates and C++20 Concepts (compile-time abstraction)
Templates abstract over types without inheritance. Concepts name the requirements explicitly:
template
concept Printable = requires(const T& t, std::ostream& os) { os << t; };
template
void log(const T& value) { std::cout << value << '\n'; }
Any Printable type works — no base class required.
4. Modules (C++20)
Modules let you export only the symbols that form the public interface; implementation helpers are invisible to importers, enforcing abstraction at the translation-unit level.
Choosing between mechanisms
| Need runtime dispatch? | Closed or open set of types? | Preferred mechanism |
|---|---|---|
| Yes | Open (new types added freely) | Abstract class + virtual functions |
| No | Closed (known at compile time) | std::variant + std::visit |
| No | Open | Templates + Concepts |
Follow-up 1
What is an abstract class in C++?
An abstract class in C++ is a class that cannot be instantiated and is used as a base class for other classes. It provides a common interface for all the derived classes. Abstract classes are declared using the 'class' keyword followed by the 'virtual' keyword and at least one pure virtual function. They can have both member variables and member functions, including non-pure virtual functions with implementations.
Follow-up 2
Can you create an instance of an abstract class?
No, you cannot create an instance of an abstract class in C++. Abstract classes are meant to be used as base classes for other classes. They provide a common interface for all the derived classes, but they cannot be instantiated on their own. If you try to create an instance of an abstract class, you will get a compilation error.
Follow-up 3
How do you declare a function as abstract in C++?
To declare a function as abstract in C++, you need to declare it as a pure virtual function in the base class. Pure virtual functions are declared using the 'virtual' keyword followed by '= 0'. They have no implementation in the base class and must be overridden in the derived classes. Any class that contains at least one pure virtual function is considered an abstract class.
Follow-up 4
What happens if a class contains at least one pure virtual function?
If a class contains at least one pure virtual function, it is considered an abstract class. Abstract classes cannot be instantiated on their own. They are meant to be used as base classes for other classes. Any derived class that inherits from an abstract class must provide implementations for all the pure virtual functions in the base class. Failure to do so will result in a compilation error.
3. What is the difference between an interface and an abstract class in C++?
C++ does not have a separate interface keyword — both concepts are expressed using classes — but the distinction is real and commonly tested.
Abstract class
A class that has at least one pure virtual function. It cannot be instantiated directly but may also contain:
- Concrete (non-pure) virtual functions with default implementations.
- Non-virtual member functions.
- Data members.
- Constructors and a (virtual) destructor.
class Animal {
public:
virtual void speak() const = 0; // must override
virtual void breathe() const { std::cout << "breathing\n"; } // default impl
virtual ~Animal() = default;
protected:
int age_ = 0; // shared state
};
Interface (convention in C++)
A class containing only pure virtual functions and a virtual destructor — no data members, no concrete implementations. This is a convention, not a language keyword.
class Drawable {
public:
virtual void draw() const = 0;
virtual ~Drawable() = default;
};
| Interface (convention) | Abstract class | |
|---|---|---|
| Pure virtual functions | All | At least one |
| Concrete functions | None | Allowed |
| Data members | None | Allowed |
| Constructors | Usually trivial/default | May be non-trivial |
| Purpose | Pure contract | Partial implementation + contract |
C++20 Concepts — the modern alternative
For compile-time "interfaces", C++20 concepts express requirements without inheritance:
template
concept Drawable = requires(const T& t) { t.draw(); };
Any type satisfying Drawable can be used in concept-constrained templates — no vtable, no inheritance, zero runtime overhead.
Interview tip: mention that you prefer interface-style (pure-virtual-only) base classes as secondary bases in multiple inheritance to avoid the diamond problem and to keep contracts clean.
Follow-up 1
Can an abstract class have a constructor?
Yes, an abstract class can have a constructor. However, since an abstract class cannot be instantiated directly, the constructor of an abstract class is usually called by the constructors of its derived classes.
Follow-up 2
Can an interface have a constructor?
No, an interface cannot have a constructor. Interfaces in C++ are purely abstract and do not have any implementation or state. They only define the contract for classes to implement.
Follow-up 3
Can an abstract class have an implementation for some methods?
Yes, an abstract class can have an implementation for some of its member functions. These member functions are called concrete member functions and can provide default behavior for the derived classes. However, the abstract class must also have at least one pure virtual function to be considered an abstract class.
Follow-up 4
Can an interface have an implementation for some methods?
No, an interface in C++ cannot have an implementation for any of its methods. Interfaces only define the contract for classes to implement and do not provide any default behavior.
4. What is the role of virtual functions in abstraction?
Virtual functions are the primary runtime mechanism for abstraction in C++. They allow a base class to declare what operations are available without specifying how each derived class implements them.
Pure virtual functions define the abstract contract
class Renderer {
public:
virtual void beginFrame() = 0;
virtual void drawMesh(const Mesh& m) = 0;
virtual void endFrame() = 0;
virtual ~Renderer() = default;
};
Renderer cannot be instantiated. It declares the interface a rendering back-end must satisfy. Application code programs against Renderer& — the concrete type (OpenGL, Vulkan, DirectX) is an implementation detail resolved at runtime.
Non-pure virtual functions provide optional defaults
class Logger {
public:
virtual void log(std::string_view msg) { std::cerr << msg << '\n'; }
virtual ~Logger() = default;
};
Derived classes may override log but are not required to; the base provides a sensible default. This is a common pattern for extensible hooks.
The vtable mechanism
At runtime, every polymorphic object contains a hidden pointer (vptr) to its class's vtable — an array of function pointers. A virtual call resolves the actual function through the vtable at call time, decoupling the caller from the concrete type.
How this supports abstraction
- The caller knows only the interface (
Renderer&); the vtable hides the concrete type. - Swapping one
Rendererimplementation for another requires no changes in calling code. - Adding a new operation to the abstract interface forces all derived classes to implement it (compile error if they don't), keeping implementations consistent with the contract.
Modern complement: concepts (C++20)
For compile-time abstraction, concepts express requirements without virtual functions:
template
concept RendererConcept = requires(R& r, const Mesh& m) {
r.beginFrame(); r.drawMesh(m); r.endFrame();
};
Use virtual functions when the concrete type must be chosen at runtime; use concepts when it is fixed at compile time and you want zero overhead.
Follow-up 1
What is a pure virtual function?
A pure virtual function is a virtual function that is declared in a base class but has no implementation. It is denoted by appending '= 0' to the function declaration. A class containing one or more pure virtual functions is called an abstract class, and cannot be instantiated. The purpose of a pure virtual function is to provide a common interface that derived classes must implement, ensuring that they provide their own implementation of the function.
Follow-up 2
How does a pure virtual function enforce abstraction?
A pure virtual function enforces abstraction by requiring derived classes to provide their own implementation. When a class contains a pure virtual function, any derived class that wants to be instantiated must provide an implementation for that function. This ensures that the derived classes adhere to the common interface defined by the base class, while still allowing each derived class to have its own specific implementation.
Follow-up 3
What is the difference between a virtual function and a pure virtual function?
The main difference between a virtual function and a pure virtual function is that a virtual function has an implementation in the base class, while a pure virtual function does not. A virtual function can be overridden by derived classes, but it can also be called directly on objects of the base class. On the other hand, a pure virtual function must be overridden by derived classes, and the base class cannot be instantiated.
Follow-up 4
Can you provide an example of a pure virtual function?
Sure! Here's an example of a pure virtual function in C++:
#include
class Shape {
public:
virtual void draw() const = 0;
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};
int main() {
// Shape shape; // Error: Cannot instantiate abstract class
Circle circle;
shape.draw(); // Output: "Drawing a circle"
return 0;
}
In this example, the Shape class is an abstract class with a pure virtual function draw(). The Circle class is a derived class that overrides the draw() function to provide its own implementation. Note that attempting to instantiate an object of the Shape class will result in a compilation error, as it is an abstract class.
5. How does abstraction support the principle of 'program to an interface, not an implementation'?
"Program to an interface, not an implementation" means callers depend on an abstraction — what something does — rather than on a specific class — how it does it. C++ abstraction directly enables this.
How abstract classes enforce it
// Interface (abstraction)
class MessageQueue {
public:
virtual void publish(std::string_view msg) = 0;
virtual std::optional consume() = 0;
virtual ~MessageQueue() = default;
};
// Two implementations — callers never see these directly
class InMemoryQueue : public MessageQueue { /* ... */ };
class KafkaQueue : public MessageQueue { /* ... */ };
// High-level code programs to MessageQueue, not to either implementation
class OrderProcessor {
public:
explicit OrderProcessor(MessageQueue& q) : queue_(q) {}
void process() { if (auto msg = queue_.consume()) handle(*msg); }
private:
MessageQueue& queue_;
void handle(const std::string& msg);
};
OrderProcessor compiles once against MessageQueue. Switching from InMemoryQueue (for tests) to KafkaQueue (for production) requires no changes to OrderProcessor.
Practical benefits
- Testability — inject a mock
MessageQueuein unit tests; swap the real one in production. - Replaceability — the concrete implementation can be upgraded or replaced without touching callers.
- Parallel development — teams can develop against the interface before the implementation exists.
Compile-time variant: C++20 Concepts
When runtime dispatch is not needed, concepts enforce the same principle at compile time with no overhead:
template
concept Queue = requires(Q& q, std::string_view msg) {
q.publish(msg);
{ q.consume() } -> std::same_as>;
};
template
void process(Q& q) { /* depends only on the Queue concept, not a specific type */ }
Any type satisfying Queue works — no base class, no vtable.
Key interview point
This principle is the foundation of the Dependency Inversion Principle (the D in SOLID). In C++, abstract base classes with pure virtual functions are the classical vehicle; C++20 concepts extend the same idea to compile-time polymorphism.
Follow-up 1
How does this principle improve code flexibility?
This principle improves code flexibility by decoupling the code from specific implementations. By programming to an interface, we can easily swap different implementations of the interface without changing the code that uses it. This allows us to introduce new implementations or modify existing ones without affecting the rest of the codebase. It also makes the code more modular and easier to maintain, as each implementation can be developed and tested independently.
Follow-up 2
Can you give an example of programming to an interface?
Sure! Let's say we have an interface called 'Shape' with a method called 'calculateArea()'. We can have multiple classes that implement this interface, such as 'Circle' and 'Rectangle'. By programming to the 'Shape' interface, we can write code that works with any object that implements the 'Shape' interface, without needing to know the specific implementation details. For example:
Shape circle = new Circle();
Shape rectangle = new Rectangle();
double circleArea = circle.calculateArea();
double rectangleArea = rectangle.calculateArea();
In this example, we are programming to the 'Shape' interface, not the specific implementations of 'Circle' and 'Rectangle'. This allows us to easily switch between different shapes without changing the code that uses them.
Follow-up 3
How does abstraction contribute to loose coupling in code?
Abstraction contributes to loose coupling in code by reducing the dependencies between different components. When we program to an interface, we only depend on the interface itself, not on the specific implementations. This allows us to easily replace or modify the implementations without affecting the rest of the code. It also makes the code more modular and easier to test, as each component can be developed and tested independently. Loose coupling improves code maintainability, reusability, and flexibility.
Follow-up 4
What is the relationship between abstraction and polymorphism in this context?
In the context of 'program to an interface, not an implementation', abstraction and polymorphism are closely related. Abstraction allows us to define a common interface that multiple classes can implement, while polymorphism allows us to treat objects of different classes that implement the same interface interchangeably. By programming to the interface, we can use polymorphism to invoke the methods defined in the interface on any object that implements it, without needing to know the specific implementation details. This promotes code flexibility and extensibility, as new classes can be easily added as long as they implement the interface.
Live mock interview
Mock interview: Abstraction in C++
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.