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

C++ Interface Design

Interface design is planning what abilities a class must offer without saying how, like a job description. Different classes can then fill the role in their own way.
Syntax
cpp
class InterfaceName {
public:
  virtual return_type method_name() = 0;
  virtual ~InterfaceName() {}
};

class Implementation : public InterfaceName {
public:
  return_type method_name() override { }
};

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 std::cout and std::cin
#include <iostream>

// Define the type Printable
class Printable {
// Members below can be accessed from outside the class
public:
	virtual void print() = 0;
};

// Define the type Document
class Document : public Printable {
// Members below can be accessed from outside the class
public:
	void print() override { std::cout << "Printing document" << std::endl; }
};

// Program execution starts in main()
int main() {
	Document d;
	d.print();
	// Return 0 to signal that the program finished successfully
	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 std::cout and std::cin
#include <iostream>

// Define the type Drawable
class Drawable {
// Members below can be accessed from outside the class
public:
	virtual void draw() = 0;
};

// Define the type Circle
class Circle : public Drawable {
// Members below can be accessed from outside the class
public:
	void draw() override { std::cout << "Circle" << std::endl; }
};

// Program execution starts in main()
int main() {
	Circle c;
	c.draw();
	// Return 0 to signal that the program finished successfully
	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 std::cout and std::cin
#include <iostream>

// Define the type Printable
class Printable {
// Members below can be accessed from outside the class
public:
	virtual void print() = 0;
};

// Define the type Savable
class Savable {
// Members below can be accessed from outside the class
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; }
};

// Program execution starts in main()
int main() {
	Document d;
	d.print();
	d.save();
	// Return 0 to signal that the program finished successfully
	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 std::cout and std::cin
#include <iostream>
// Include std::string
#include <string>

// Define the type Logger
class Logger {
// Members below can be accessed from outside the class
public:
	virtual void log(std::string msg) = 0;
};

// Define the type ConsoleLogger
class ConsoleLogger : public Logger {
// Members below can be accessed from outside the class
public:
	void log(std::string msg) override { std::cout << msg << std::endl; }
};

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

// Program execution starts in main()
int main() {
	ConsoleLogger logger;
	process(logger);
	// Return 0 to signal that the program finished successfully
	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 std::cout and std::cin
#include <iostream>

// Define the type Notifier
class Notifier {
// Members below can be accessed from outside the class
public:
	virtual void notify() = 0;
};

// Define the type EmailNotifier
class EmailNotifier : public Notifier {
// Members below can be accessed from outside the class
public:
	void notify() override { std::cout << "Email sent" << std::endl; }
};

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

// Program execution starts in main()
int main() {
	EmailNotifier email;
	App app(email);
	app.run();
	// Return 0 to signal that the program finished successfully
	return 0;
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Trying to create an instance of an interface class made only of pure virtual functions.
  2. Forgetting to implement all the interface functions in the concrete class.
  3. Adding data members to an interface, which mixes interface and implementation.

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.