Dependency Injection Container for C++: Overview and Practical Examples
In modern C++ development, the principles of Inversion of Control (IoC) and Dependency Injection (DI) are increasingly used. These patterns allow you to create loosely coupled, testable, and easily extensible code. Unlike languages with managed memory (Java, C#), C++ does not have built-in reflection, so implementing DI containers here requires template metaprogramming and the use of modern standards (C++11/14/17/20).
In this article, we will look at three popular approaches to dependency injection in C++: the Boost.DI, Google Fruit, and Kangaru libraries, and also write our own simple container to understand the mechanics.
What is Dependency Injection?
Dependency Injection is a technique where an object receives its dependencies from the outside, rather than creating them itself. The main types of DI are:
- Constructor Injection — dependencies are passed through the constructor.
- Setter Injection — dependencies are set through setters.
- Interface Injection — dependencies are passed through an interface.
Example without DI (tight coupling):
class Logger {public: void log(const std::string& msg) { /* write to file */ }};
class Service { Logger logger; // Tight dependencypublic: void doWork() { logger.log("Work done"); }};Example with DI (loose coupling):
class ILogger {public: virtual ~ILogger() = default; virtual void log(const std::string& msg) = 0;};
class ConsoleLogger : public ILogger {public: void log(const std::string& msg) override { std::cout << msg << std::endl; }};
class Service { std::shared_ptr<ILogger> logger;public: explicit Service(std::shared_ptr<ILogger> l) : logger(std::move(l)) {} void doWork() { logger->log("Work done"); }};Overview of DI Containers for C++
1. Boost.DI
Boost.DI is one of the most popular libraries. It uses C++14/17 templates for automatic dependency resolution. It supports polymorphism, scopes (singleton, unique), and modular configuration.
Usage example:
#include <boost/di.hpp>#include <iostream>
namespace di = boost::di;
class IWriter {public: virtual ~IWriter() = default; virtual void write(const std::string&) = 0;};
class ConsoleWriter : public IWriter {public: void write(const std::string& msg) override { std::cout << msg << std::endl; }};
class App { std::shared_ptr<IWriter> writer;public: explicit App(std::shared_ptr<IWriter> w) : writer(std::move(w)) {} void run() { writer->write("Hello from Boost.DI!"); }};
int main() { auto injector = di::make_injector( di::bind<IWriter>.to<ConsoleWriter>() ); auto app = injector.create<std::unique_ptr<App>>(); app->run(); return 0;}Pros: strong typing, minimal runtime overhead, support for C++14/17.
Cons: complex compilation error messages, increased build time.
2. Google Fruit
Google Fruit is a container from Google that works on C++11. It uses macros for component registration and supports constructor injection.
Example:
#include <fruit/fruit.h>#include <iostream>
class IWriter {public: virtual void write(const std::string&) = 0;};
class ConsoleWriter : public IWriter {publi