Tutorials Logic, IN info@tutorialslogic.com

C++ Abstraction with Interfaces and Abstract Classes

C++ Abstraction Boundary

An abstraction presents the operations a caller needs while hiding implementation choices. In C++, abstract base classes, templates, and ordinary value types can all define an interface. Choose runtime polymorphism only when implementations must vary behind one stable binary or ownership boundary.

Inject a Narrow Interface

Depending on a small interface makes the caller testable without inheriting from a large concrete service.

Abstract storage behind two operations

Abstract storage behind two operations
#include <iostream>
#include <string>
#include <string_view>
#include <utility>

struct Store {
    virtual ~Store() = default;
    virtual void put(std::string key, std::string value) = 0;
    virtual std::string get(std::string_view key) const = 0;
};

struct DemoStore : Store {
    void put(std::string, std::string value) override { saved = std::move(value); }
    std::string get(std::string_view) const override { return saved; }
    std::string saved;
};

int main() { DemoStore store; store.put("status", "ready"); std::cout << store.get("status") << '\n'; }
Output
ready

Avoid Leaky Interfaces

If every caller must know which concrete subtype exists or must call methods in a hidden sequence, the abstraction is not carrying its contract. Prefer cohesive operations and return types that represent failure explicitly.

  • Keep interface state minimal.
  • Do not add getters for every private field.
  • Separate unrelated capabilities into smaller interfaces.

Interface Design

A runtime interface usually has a public virtual destructor and pure virtual operations. Keep it small, avoid shared mutable base state, and pass ownership with smart pointers whose type states whether transfer occurs. A protected non-virtual destructor is another specialized design, but it intentionally prevents deletion through the base.

Templates provide compile-time abstraction and can preserve value semantics without virtual dispatch. Concepts can state required operations clearly. Prefer the simplest form that matches when the implementation choice is known and whether heterogeneous objects must share one collection.

Test the Contract Through Substitution

An abstraction succeeds when callers can use another implementation without learning new rules. Express the smallest stable behavior in the interface, keep storage and transport details behind it, and test every implementation with the same contract suite.

Do not add getters merely to expose internals for a caller. Add the operation the caller needs, or move the responsibility when the abstraction cannot own that behavior coherently.

Swap Implementations Behind One Interface

Swap Implementations Behind One Interface
#include <iostream>
#include <string>

struct Notifier {
    virtual ~Notifier() = default;
    virtual std::string send() const = 0;
};
struct Email : Notifier { std::string send() const override { return "email"; } };
struct Console : Notifier { std::string send() const override { return "console"; } };

void deliver(const Notifier& notifier) { std::cout << notifier.send() << '\n'; }
int main() { Email email; Console console; deliver(email); deliver(console); }
Output
email
console

The caller depends only on the behavioral contract and works unchanged with both implementations.

Before you move on

Interface Review

5 checks
  • Callers depend on behavior, not storage.
  • The destructor policy supports intended ownership.
  • The interface is narrow and cohesive.
  • Failure is part of the contract.
  • Tests can supply a small substitute.

Try this next

Abstraction Practice

0 of 2 completed

  1. Define a small storage interface, implement memory and file-backed variants, and run the same caller test suite against both. The caller should depend only on the operation contract.
  2. Find a public method that exposes a concrete container or file path and replace it with the behavior the caller actually needs. An abstraction is weak when callers must understand its internal representation.

Abstraction Questions

A class is abstract while it has at least one unimplemented pure virtual function.

Yes. Derived classes must still override it, but the base definition can provide shared behavior when called explicitly.

Mostly pure virtual functions and a virtual destructor, with little or no mutable state.

Next Step
Next Practice

Finish the concept here, then reinforce it with hands-on coding, interview prep, or a tool that matches the topic.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.