← Back to C++ Course | Chapter 10: Polymorphism & Abstraction | Lesson 9 of 11

C++ Interface Design

Pure Interfaces

An interface in C++ is conventionally represented by an abstract class made up entirely of pure virtual functions with no data members of its own — it establishes a contract that every derived class implementing it must fulfill.

Example: Pure Interfaces

cpp
#include <iostream>

class Printable {
public:
	virtual void print() = 0;
};

class Document : public Printable {
public:
	void print() override { std::cout << "Printing document" << std::endl; }
};

int main() {
	Document d;
	d.print();
	return 0;
}

Implementing Interfaces

To implement an interface, a class inherits from the interface class and supplies a concrete override for every one of its pure virtual methods; only once all of them are implemented does the derived class become instantiable.

Example: Implementing Interfaces

cpp
#include <iostream>

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

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

int main() {
	Circle c;
	c.draw();
	return 0;
}

Multiple Interfaces

Unlike languages that limit a class to one base class, C++ allows multiple inheritance, so a single class can implement several distinct interfaces at once, each contributing its own separate contract that the class must satisfy.

Example: Multiple Interfaces

cpp
#include <iostream>

class Printable {
public:
	virtual void print() = 0;
};

class Savable {
public:
	virtual void save() = 0;
};

class Document : public Printable, public Savable {
public:
	void print() override { std::cout << "Printing" << std::endl; }
	void save() override { std::cout << "Saving" << std::endl; }
};

int main() {
	Document d;
	d.print();
	d.save();
	return 0;
}

Loose Coupling

Designing against interfaces rather than concrete classes promotes loose coupling: code that only interacts through an interface pointer doesn't need to know which concrete implementation is behind it, so swapping in a different implementation later doesn't ripple through the rest of the program.

Example: Loose Coupling

cpp
#include <iostream>
#include <string>

class Logger {
public:
	virtual void log(std::string msg) = 0;
};

class ConsoleLogger : public Logger {
public:
	void log(std::string msg) override { std::cout << msg << std::endl; }
};

void process(Logger &logger) {
	logger.log("Processing complete");
}

int main() {
	ConsoleLogger logger;
	process(logger);
	return 0;
}

Dependency Inversion

This style of design supports the Dependency Inversion principle, which says high-level modules should depend on abstract interfaces rather than directly on low-level concrete classes — making the overall system easier to extend and easier to test in isolation.

Example: Dependency Inversion

cpp
#include <iostream>

class Notifier {
public:
	virtual void notify() = 0;
};

class EmailNotifier : public Notifier {
public:
	void notify() override { std::cout << "Email sent" << std::endl; }
};

class App {
	Notifier &notifier;
public:
	App(Notifier &n) : notifier(n) {}
	void run() { notifier.notify(); }
};

int main() {
	EmailNotifier email;
	App app(email);
	app.run();
	return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.