Внедрение зависимостей (Dependency Injection) в C++: контейнеры и практика

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Dependency Injection контейнер для C++: обзор и практические примеры

В современной разработке на C++ всё чаще применяются принципы Inversion of Control (IoC) и Dependency Injection (DI). Эти паттерны позволяют создавать слабосвязанный, тестируемый и легко расширяемый код. В отличие от языков с управляемой памятью (Java, C#), C++ не имеет встроенной рефлексии, поэтому реализация DI-контейнеров здесь требует метапрограммирования шаблонов и использования современных стандартов (C++11/14/17/20).

В этой статье мы рассмотрим три популярных подхода к внедрению зависимостей в C++: библиотеки Boost.DI, Google Fruit и Kangaru, а также напишем собственный простой контейнер для понимания механики.

Что такое Dependency Injection?

Dependency Injection — это техника, при которой объект получает свои зависимости извне, а не создаёт их сам. Основные виды DI:

  • Constructor Injection — зависимости передаются через конструктор.
  • Setter Injection — зависимости устанавливаются через сеттеры.
  • Interface Injection — зависимости передаются через интерфейс.

Пример без DI (жёсткая связь):

class Logger {
public:
    void log(const std::string& msg) { /* запись в файл */ }
};

class Service {
    Logger logger; // Жёсткая зависимость
public:
    void doWork() { logger.log("Work done"); }
};

Пример с DI (слабая связь):

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"); }
};

Обзор DI-контейнеров для C++

1. Boost.DI

Boost.DI — одна из самых популярных библиотек. Она использует шаблоны C++14/17 для автоматического разрешения зависимостей. Поддерживает полиморфизм, области видимости (singleton, unique) и модульную конфигурацию.

Пример использования:

#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;
}

Плюсы: мощная типизация, минимальный runtime overhead, поддержка C++14/17.

Минусы: сложные сообщения об ошибках компиляции, увеличение времени сборки.

2. Google Fruit

Google Fruit — контейнер от Google, работающий на C++11. Использует макросы для регистрации компонентов и поддерживает инъекцию через конструктор.

Пример:

#include <fruit/fruit.h>
#include <iostream>

class IWriter {
public:
    virtual void write(const std::string&) = 0;
};

class ConsoleWriter : public IWriter {
public:
    INJECT(ConsoleWriter()) = default;
    void write(const std::string& msg) override {
        std::cout << msg << std::endl;
    }
};

class App {
    IWriter* writer;
public:
    INJECT(App(ASSISTED(IWriter*) writer)) : writer(writer) {}
    void run() { writer->write("Hello from Fruit!"); }
};

fruit::Component<App> getAppComponent() {
    return fruit::createComponent()
        .bind<IWriter, ConsoleWriter>();
}

int main() {
    fruit::Injector<App> injector(getAppComponent);
    App& app = injector.get<App>();
    app.run();
    return 0;
}

Плюсы:

Рекомендаций