Question
How to Declare an Interface in C++: Abstract Base Classes Explained
Question
How do you set up a class in C++ that represents an interface? Is an interface in C++ simply an abstract base class?
Short Answer
By the end of this page, you will understand how C++ represents interfaces using abstract base classes, what pure virtual functions do, why virtual destructors matter, and how concrete classes implement those interfaces in real code.
Concept
In C++, there is no separate interface keyword like in some other languages such as Java or C#. Instead, the usual way to model an interface is to create an abstract base class.
A class becomes abstract when it contains at least one pure virtual function. A pure virtual function is declared by assigning = 0 at the end of the function declaration.
class Shape {
public:
virtual double area() const = 0;
};
This means:
Shapecannot be instantiated directly.- Any derived class must provide an implementation for
area()unless it is also abstract. - The class acts as a contract: derived classes promise to provide certain behavior.
In practice, an interface-style class in C++ usually has:
- only pure virtual functions
- no data members
- a
virtualdestructor
For example:
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log(const std::string& message) = 0;
};
This pattern matters because it lets you write code against a common contract instead of a specific implementation. That makes code easier to extend, test, and reuse.
For example, code can depend on ILogger without caring whether the real object writes to a file, the console, or a remote service.
Although an interface in C++ is usually an abstract base class, not every abstract base class is used purely as an interface. Some abstract base classes also contain shared implementation or member data. So the key idea is:
- Interface-style class: abstract base class used mainly as a contract
- General abstract base class: abstract class that may also provide reusable implementation
Mental Model
Think of an interface in C++ like a job description.
The interface says:
- what tasks must be done
- what function names and signatures must exist
- but not exactly how the work is performed
For example, a Printer interface might say every printer must support print().
A laser printer and a PDF printer both satisfy the same job description, but they do the work differently.
So:
- the interface is the contract
- the derived class is the worker fulfilling that contract
- the pure virtual functions are the required tasks
This is why abstract base classes are used for interfaces in C++: they define required behavior without forcing one specific implementation.
Syntax and Examples
Basic interface-style class in C++
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
What this means
IAnimalis abstract becausespeak()is pure virtual.virtual ~IAnimal() = default;makes destruction safe through a base-class pointer.- You cannot create an
IAnimaldirectly.
Implementing the interface
#include <iostream>
using namespace std;
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
class Dog : public IAnimal {
public:
void speak() const override {
cout << "Woof\n";
}
};
int main() {
Dog d;
d.speak();
}
Using polymorphism
#include <iostream>
#include <memory>
using namespace std;
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
class Dog : public IAnimal {
public:
void speak() const override {
cout << "Woof\n";
}
};
class Cat : public IAnimal {
public:
void speak() const override {
cout << "Meow\n";
}
};
int main() {
unique_ptr<IAnimal> a1 = make_unique<Dog>();
unique_ptr<IAnimal> a2 = make_unique<Cat>();
a1->speak();
a2->speak();
}
Here, both Dog and Cat can be treated as IAnimal. The calling code uses the interface without needing to know the exact class.
Interface with multiple functions
class IShape {
public:
virtual ~IShape() = default;
virtual double area() const = 0;
virtual double perimeter() const = 0;
};
Any class implementing IShape must provide both functions.
Step by Step Execution
Consider this example:
#include <iostream>
using namespace std;
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
class Dog : public IAnimal {
public:
void speak() const override {
cout << "Woof\n";
}
};
int main() {
IAnimal* animal = new Dog();
animal->speak();
delete animal;
}
Step-by-step
-
IAnimalis declared with a pure virtual functionspeak().- Because of
= 0,IAnimalis abstract. - You cannot write
IAnimal animal;
- Because of
-
Doginherits fromIAnimal.Dogprovides an implementation ofspeak().- That makes
Doga concrete class.
-
In
main(),new Dog()creates aDogobject. -
The pointer type is
IAnimal*.- This is important: the code stores a
Dogas a pointer to the interface. - This allows code to work with any class implementing
IAnimal.
- This is important: the code stores a
-
animal->speak();calls theDogversion ofspeak().- Because the function is
virtual, C++ uses dynamic dispatch. - Output:
Woof
- Because the function is
-
delete animal;destroys the object.- Because the destructor in
IAnimalis virtual, the correct derived destructor would run. - This prevents undefined behavior and resource leaks in real programs.
- Because the destructor in
Key lesson
The interface defines the required behavior, and virtual dispatch makes the correct derived implementation run at runtime.
Real World Use Cases
Interfaces in C++ are commonly used when multiple classes should provide the same behavior in different ways.
Common examples
-
Logging systems
ILoggercan be implemented byConsoleLogger,FileLogger, orNetworkLogger.
-
Payment providers
IPaymentProcessorcan be implemented by Stripe, PayPal, or a mock test processor.
-
Game engines
IRenderermight have OpenGL, Vulkan, or DirectX implementations.
-
Device drivers
- A common interface can describe devices that read data, while each device class handles hardware-specific details.
-
Testing and mocking
- If your code depends on an interface, you can replace the real class with a fake or mock version in tests.
Example scenario
class IDataStore {
public:
virtual ~IDataStore() = default;
virtual void save(const std::string& value) = 0;
};
Possible implementations:
- save to a file
- save to a database
- save to memory for testing
The rest of the code only depends on IDataStore, not on one storage method.
Real Codebase Usage
In real projects, interface-style classes are often used to separate what a system does from how it does it.
Common patterns
Dependency injection
A class receives an interface instead of creating a concrete dependency directly.
class ILogger {
public:
virtual ~ILogger() = default;
virtual void log(const std::string& message) = 0;
};
class Service {
private:
ILogger& logger;
public:
Service(ILogger& logger) : logger(logger) {}
void run() {
logger.log("Service started");
}
};
This makes Service easier to test and reuse.
Testing with fake implementations
class FakeLogger : public ILogger {
public:
void log(const std::string& message) override {
// store message for test verification
}
};
Plug-in style architecture
Applications may define interfaces for modules, then load different implementations depending on configuration.
Stable API boundaries
Interfaces help large codebases keep modules loosely coupled. One team can change an implementation without forcing all callers to change.
Design notes developers often follow
- Prefer small, focused interfaces.
- Add a virtual destructor.
- Use
overridein derived classes. - Avoid putting unnecessary data in interface-only classes.
- Pass interfaces by reference or pointer when polymorphism is needed.
Common Mistakes
1. Forgetting the virtual destructor
This is one of the most important mistakes.
Broken code
class IAnimal {
public:
virtual void speak() const = 0;
};
If you delete a derived object through an IAnimal*, behavior is unsafe because the base destructor is not virtual.
Correct code
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
2. Trying to instantiate the interface
Broken code
IAnimal animal;
This fails because IAnimal is abstract.
Fix
Instantiate a concrete derived class instead.
Dog dog;
3. Forgetting to implement all pure virtual functions
Broken code
class IShape {
public:
virtual ~IShape() = default;
virtual double area() const = 0;
};
class Circle : public IShape {
// area() missing
};
Circle is still abstract if it does not implement area().
4. Signature mismatch when overriding
Broken code
class IAnimal {
public:
virtual ~IAnimal() = default;
virtual void speak() const = 0;
};
class Dog : public IAnimal {
public:
void speak() override {
// missing const
}
};
This does not correctly override the base function.
Fix
class Dog : public IAnimal {
public:
void speak() const override {
}
};
Using override helps the compiler catch mistakes.
5. Adding data members to what should be a pure interface
This is not always wrong, but if your goal is a clean interface, adding state can make the class less flexible.
6. Confusing abstract base classes with interface-only classes
An abstract base class may include shared code and member variables. An interface-style class is usually kept minimal and contract-focused.
Comparisons
| Concept | What it means | Typical contents | Can contain implementation? | Main use |
|---|---|---|---|---|
| Interface-style class in C++ | An abstract base class used as a contract | Pure virtual functions, virtual destructor | Usually no | Define required behavior |
| Abstract base class | A class that cannot be instantiated | One or more pure virtual functions, sometimes data and concrete methods | Yes | Shared design and reusable base behavior |
| Concrete class | A fully implemented class | Normal methods and data members | Yes | Create actual objects |
Interface-style class vs abstract base class
- Every interface-style class in C++ is typically an abstract base class.
- But not every abstract base class is a pure interface.
- Some abstract base classes include common logic for derived classes.
Pure virtual vs regular virtual
| Function type | Meaning |
|---|---|
virtual void f(); | Can be overridden, but does not force derived classes to override |
virtual void f() = 0; | Must be implemented by concrete derived classes |
C++ vs languages with an interface keyword
| Language style | How interfaces are declared |
|---|---|
| C++ | Usually with an abstract base class and pure virtual functions |
| Java/C# | With a dedicated interface keyword |
So the idea is similar, but the syntax is different in C++.
Cheat Sheet
Quick reference
Declare an interface-style class
class IExample {
public:
virtual ~IExample() = default;
virtual void doWork() = 0;
};
Implement it
class Example : public IExample {
public:
void doWork() override {
// implementation
}
};
Rules to remember
- C++ has no built-in
interfacekeyword. - Use an abstract base class to model an interface.
- A class becomes abstract when it has at least one pure virtual function.
- Pure virtual syntax:
virtual ReturnType name(...) = 0; - Add a
virtualdestructor for safe polymorphic deletion. - Use
overridein derived classes. - You cannot instantiate an abstract class directly.
Common pattern
class IService {
public:
virtual ~IService() = default;
virtual void run() = 0;
};
Best practices
- Keep interfaces small.
- Prefer behavior-only members.
- Pass interface objects by reference or pointer.
- Use smart pointers when ownership is involved.
Edge case
A pure virtual destructor is allowed, but it still needs a definition if used:
class Base {
public:
virtual ~Base() = 0;
};
Base::~Base() {}
Usually, virtual ~Base() = default; is simpler.
FAQ
Is there an interface keyword in C++?
No. C++ usually models interfaces with abstract base classes containing pure virtual functions.
Is an interface in C++ just an abstract base class?
Usually yes. More specifically, an interface-style class is an abstract base class used mainly to define a contract.
What makes a class abstract in C++?
A class is abstract if it has at least one pure virtual function, written with = 0.
Why should an interface have a virtual destructor?
So deleting an object through a base-class pointer works correctly and safely.
Can an abstract base class contain normal method implementations?
Yes. An abstract base class can contain both pure virtual and fully implemented methods. If it does, it is not a pure interface-only design.
Can an interface in C++ have member variables?
It can, but interface-style classes usually avoid data members and focus only on behavior.
Should I use override when implementing interface methods?
Yes. It helps the compiler detect signature mismatches and makes code clearer.
Mini Project
Description
Create a small notification system where different classes send messages in different ways, but all follow the same interface. This demonstrates how C++ interfaces let you write one piece of code that works with many implementations.
Goal
Build an interface-style class for notifications and implement at least two concrete notification types that can be used through the same base interface.
Requirements
- Create an abstract base class named
INotifier - Add a pure virtual function that sends a message
- Add a virtual destructor to the interface
- Implement at least two classes such as
EmailNotifierandSmsNotifier - Write a function that accepts the interface and sends a message through it
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.