Dependency Injection in C++: Complete Guide with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Dependency Injection in C++



In modern C++ development, one of the key principles for creating flexible and testable code is Dependency Injection (DI). This design pattern helps reduce coupling between system components, making code more modular, reusable, and easier to test.



In this article, we will explore what Dependency Injection in C++ is, what problems it solves, examine various ways to implement it in practice, and provide code examples to help you incorporate this approach into your projects.



The Problem of Direct Dependency



Before discussing the solution, let's look at a typical problem. Imagine we have a UserService class that uses a Logger for logging:



class Logger {public:    void log(const std::string& message) {        std::cout << "[LOG]: " << message << std::endl;    }};

class UserService {private: Logger logger; // Hard dependencypublic: void createUser(const std::string& name) { logger.log("Creating user: " + name); // ... user creation logic }};


Here, UserService creates a Logger instance on its own. This creates several problems:

  • Testing complexity: it's impossible to substitute the logger with a mock object during unit tests.
  • Tight coupling: changing the Logger constructor requires modifying all classes that use it.
  • SRP violation (Single Responsibility Principle): the class is responsible not only for its own logic but also for creating its dependencies.


What is Dependency Injection?



Dependency Injection is a pattern where an object receives its dependencies from the outside, rather than creating them itself. The main idea is: "don't ask, receive."



In C++, there are three main ways to inject dependencies:

  • Constructor Injection — via the constructor.
  • Setter Injection — via setter methods.
  • Interface Injection — via an interface.


1. Constructor Injection



This is the most common and preferred method. Dependencies are passed into the class constructor:



#include <memory>#include <iostream>

class ILogger {public: virtual ~ILogger() = default; virtual void log(const std::string& message) = 0;};

class ConsoleLogger : public ILogger {public: void log(const std::string& message) override { std::cout << "[CONSOLE]: " << message << std::endl; }};

class FileLogger : public ILogger {public: void log(const std::string& message) override { // File writing logic std::cout << "[FILE]: " << message << std::endl; }};

class UserService {private: std::unique_ptr<ILogger> logger_; // Dependency via interfacepublic: // Constructor Injection explicit UserService(std::unique_ptr<ILogger> logger) : logger_(std::move(logger)) {}

void createUser(const std::string& name) { logger_->log("Creating user: " + name); // ... logic }};

int main() { auto consoleLogger = std::make_unique<ConsoleLogger>(); UserService service(std::move(consoleLogger)); service.createUser("Alice"); return 0;}


Advantages:

  • The dependency is clearly visible in the constructor signature.
  • The object is always in a valid state after creation.
  • Easy to test — you can subst

Blogs

Book Recommendations