Boost.DI: внедрение зависимости в С++

в 8:57, , рубрики: c++, dependency injection, Блог компании Инфопульс Украина, Проектирование и рефакторинг

«Не звони нам. Мы позвоним тебе сами.» — принцип Голливуда

Внедрение зависимости (Dependency Injection — DI) означает передачу (внедрение) одной или более зависимости какому-либо объекту (клиенту, компоненту) таким образом, что после внедрения эта зависимость становится частью состояния объекта. Это немного напоминает паттерн проектирования Стратегия, с той лишь разницей, что стратегия задаётся лишь однажды — в конструкторе. DI позволяет создавать более слабо-связанную архитектуру приложения, которая лучше поддаётся поддержке и тестированию.

Итак, ещё раз плюсы:

  • Уменьшает связность компонент (отделяет бизнес-логику от создания объекта)
  • Позволяет писать более поддерживаемый код (в одни и те же объекты мы легко можем внедрять разные зависимости)
  • Позволяет писать более тестируемый код (мы можем легче использовать стабы и моки)
Без внедрения зависимости С внедрением зависимости
class example {                         
public:  
    example()                           
        : logic_(new logic{})           
        , logger_(                      
            logger_factory::create()    
          )                             
    { }  
         
    int run() const;                    
         
private: 
    shared_ptr<ilogic> logic_;          
    shared_ptr<ilogger> logger_;        
};                             

 class example {
 public:
     example(shared_ptr<ilogic> logic
           , shared_ptr<ilogger> logger)
       : logic_(logic), logger_(logger)
     { }

     int run() const;

 private:
     shared_ptr<ilogic> logic_;
     shared_ptr<ilogger> logger_;
 };


Boost.DI это библиотека для реализации внедрения зависимости в С++. Для её использования нужно лишь включить в свой код один заголовочный файл. Цель библиотеки — упростить создание объектов с помощью автоматического внедрения зависимостей:

  • Уменьшение количества «узких мест» в коде — никаких лишних фабрик, никакого создания объектов лишь в определённом порядке
  • Упрощение поддержки кода — изменение сигнатуры конструктора не повлияет на конфигурацию DI
  • Упрощение тестирование — автоматическая инъекция моков
  • Более хороший контроль над тем, что и когда создаётся
  • Лучшее понимание кода в плане иерархии объектов
Ручное внедрение зависимости Boost.DI
int main() {                            
    /*boilerplate code*/                
    auto logic = make_shared<logic>();  
    auto logger = make_shared<logger>();
         
    return example{logic, logger}.run();
}        

int main() {
    auto injector = di::make_injector(
        di::bind<ilogic, logic>
      , di::bind<ilogger, logger>
    );
    return injector.create<example>().run();
}

Ещё раз — зачем использовать внедрение зависимости

image

С чего начать?

  • Возьмите компилятор с поддержкой С++14 (Clang-3.4+, GCC-5.0+). Библиотека Boost не требуется
  • Прочитайте этот документ
  • Прочитайте учебник
  • Прочитайте документацию

А вообще-то всё, что вам необходимо для работы с библиотекой, этой файл di.hpp

    // main.cpp
    #include "di.hpp"
    int main() { }
    $CXX -std=c++1y -I. main.cpp

Давайте предположим, что все примеры ниже включают boost/di.hpp и определяют пространство имён di как алиас boost::di.

#include <boost/di.hpp>
namespace di = boost::di;
//
struct i1 { virtual ~i1() = default; virtual void dummy1() = 0; };
struct i2 { virtual ~i2() = default; virtual void dummy2() = 0; };
struct impl1 : i1 { void dummy1() override { } };
struct impl2 : i2 { void dummy2() override { } };
struct impl : i1, i2 { void dummy1() override { } void dummy2() override { } };

Инжектор

Создание пустого инжектора Тест
auto injector = di::make_injector();    

assert(0 == injector.create<int>());         

Привязки

Примеры
Ещё примеры

Привязка интерфейса к реализации Тест
auto injector = di::make_injector(di::bind<i1, impl1>);

auto object = injector.create<unique_ptr<i1>>();             
assert(dynamic_cast<impl1*>(object.get()));

Привязка нескольких интерфейсов к одной реализации Тест
auto injector = di::make_injector(di::bind<di::any_of<i1, i2>, impl>);     

auto object1 = injector.create<shared_ptr<i1>>();            
auto object2 = injector.create<shared_ptr<i2>>();
assert(dynamic_cast<impl*>(object1.get()));
assert(dynamic_cast<impl*>(object2.get()));

Привязка типа к значению, вычисляемому на этапе компиляции Тест
template<int N> using int_ = integral_constant<int, N>;    
auto injector = di::make_injector(di::bind<int, int_<42>>);

assert(42 == injector.create<int>());                        

Привязка типа к значению Тест
auto injector = di::make_injector(di::bind<int>.to(42));

assert(42 == injector.create<int>());                     

Внедрение

Примеры
Ещё примеры

Внедрение через обычный конструктор Тест
struct c {                             
    c(int a, double d) : a(a), d(d) { }
        
    int a = 0;                         
    double d = 0.0;                    
};      
        
auto injector = di::make_injector(     
    di::bind<int>.to(42)               
  , di::bind<double>.to(87.0)          
);

auto object = injector.create<c>();
assert(42 == object.a);
assert(87.0 == object.d);

Внедрение через аггрегацию Тест
struct c {                        
    int a = 0;                    
    double d = 0.0;               
}; 
   
auto injector = di::make_injector(
    di::bind<int>.to(42)          
  , di::bind<double>.to(87.0)     
);          

auto object = injector.create<c>();
assert(42 == object.a);
assert(87.0 == object.d);

Внедрение через конструктор при наличии
нескольких конструкторов (будет выбран тот, у которого
наибольшее число параметров)
Тест
struct c {                             
    c();                               
    c(int a) : a(a) { }                
    c(int a, double d) : a(a), d(d) { }
        
    int a = 0;                         
    double d = 0.0;                    
};      
        
auto injector = di::make_injector(     
    di::bind<int>.to(42)               
  , di::bind<double>.to(87.0)          
);

auto object = injector.create<c>();             
assert(42 == object.a);
assert(87.0 == object.d);

Внедрение через конструктор при наличии вариантов
выбора (использование BOOST_DI_INJECT)
Тест
struct c {                             
    c(double d, int a) : a(a), d(d) { }
    BOOST_DI_INJECT(c, int a, double d)
        : a(a), d(d) { }               
        
    int a = 0;                         
    double d = 0.0;                    
};      
        
auto injector = di::make_injector(     
    di::bind<int>.to(42)               
  , di::bind<double>.to(87.0)          
);

auto object = injector.create<c>();        
assert(42 == object.a);
assert(87.0 == object.d);

Внедрение через конструктор при наличии вариантов
выбора (использование BOOST_DI_INJECT_TRAITS)
Тест
struct c {                              
    BOOST_DI_INJECT_TRAITS(int, double);
    c(double d, int a) : a(a), d(d) { } 
    c(int a, double d) : a(a), d(d) { } 
         
    int a = 0;                          
    double d = 0.0;                     
};       
         
auto injector = di::make_injector(      
    di::bind<int>.to(42)                
  , di::bind<double>.to(87.0)           
);

auto object = injector.create<c>();        
assert(42 == object.a);
assert(87.0 == object.d);

Внедрение через конструктор при наличии вариантов
выбора (использование di::ctor_traits)
Тест
struct c {                              
    c(double d, int a) : a(a), d(d) { } 
    c(int a, double d) : a(a), d(d) { } 
         
    int a = 0;                          
    double d = 0.0;                     
};       
         
namespace boost { namespace di {        
template<>                              
struct ctor_traits<c> {                 
    BOOST_DI_INJECT_TRAITS(int, double);
};       
}} // boost::di                         
         
auto injector = di::make_injector(      
    di::bind<int>.to(42)                
  , di::bind<double>.to(87.0)           
);       

auto object = injector.create<c>();       
assert(42 == object.a);
assert(87.0 == object.d);

Аннотации

Примеры

Внедрение через аннотированные конструкторы Тест
auto int1 = []{};                   
auto int2 = []{};                   
     
struct c {                          
    BOOST_DI_INJECT(c               
        , (named = int1) int a      
        , (named = int2) int b)     
        : a(a), b(b)                
    { }                             
     
    int a = 0;                      
    int b = 0;                      
};   
     
auto injector = di::make_injector(  
    di::bind<int>.named(int1).to(42)
  , di::bind<int>.named(int2).to(87)
);   

auto object = injector.create<c>();
assert(42 == object.a);
assert(87 == object.b);

Внедрение через аннотированные конструкторы с
использованием именованных параметров
Тест
auto n1 = []{};                         
auto n2 = []{};                         
         
struct c {                              
  BOOST_DI_INJECT(c                     
      , (named = n1) int a              
      , (named = n1) int b              
      , (named = n2) int c              
      , int d                           
      , (named = n1) string s)          
  : i1(i1), i2(i2), i3(i3), i4(i4), s(s)
  { }    
         
  int i1 = 0;                           
  int i2 = 0;                           
  int i3 = 0;                           
  int i4 = 0;                           
  string s;                             
};       
         
auto injector = di::make_injector(      
    di::bind<int>.named(n1).to(42)      
  , di::bind<int>.named(n2).to(87)      
  , di::bind<string>.named(n1).to("str")
);       

auto object = injector.create<c>();          
assert(42 == object.i1);
assert(42 == object.i2);
assert(87 == object.i3);
assert(0 == object.i4);
assert("str" == c.s);

Внедрение через аннотированные конструкторы с
вынесением реализации конструктора
Тест
auto int1 = []{};                   
auto int2 = []{};                   
     
struct c {                          
    BOOST_DI_INJECT(c               
        , (named = int1) int a      
        , (named = int2) int b);    
     
    int a = 0;                      
    int b = 0;                      
};   
     
c::c(int a, int b) : a(a), b(b) { } 
     
auto injector = di::make_injector(  
    di::bind<int>.named(int1).to(42)
  , di::bind<int>.named(int2).to(87)
);           

auto object = injector.create<c>();       
assert(42 == object.a);
assert(87 == object.b);

Внедрение через аннотированные конструкторы с
использованием di::ctor_traits
Тест
auto int1 = []{};                   
auto int2 = []{};                   
     
struct c {                          
    c(int a, int b) : a(a), b(b) { }
     
    int a = 0;                      
    int b = 0;                      
};   
     
namespace boost { namespace di {    
template<>                          
struct ctor_traits<c> {             
    BOOST_DI_INJECT_TRAITS(         
        (named = int1) int          
      , (named = int2) int);        
};   
}} // boost::di                     
     
auto injector = di::make_injector(  
    di::bind<int>.named(int1).to(42)
  , di::bind<int>.named(int2).to(87)
);                

auto object = injector.create<c>();         
assert(42 == object.a);
assert(87 == object.b);

Области видимости

Примеры
Ещё примеры

Вывод области видимости (по-умолчанию) Тест
struct c {                          
    shared_ptr<i1> sp; /*singleton*/
    unique_ptr<i2> up; /*unique*/   
    int& i; /*external*/            
    double d; /*unique*/            
};   
     
auto i = 42;                        
     
auto injector = di::make_injector(  
    di::bind<i1, impl1>             
  , di::bind<i2, impl2>             
  , di::bind<int>.to(ref(i))        
  , di::bind<double>.to(87.0)       
);                      

auto object1 = injector.create<unique_ptr<c>>();
auto object2 = injector.create<unique_ptr<c>>();
assert(object1->sp == object2->sp);
assert(object1->up != object2->up);
assert(42 == object1->i);
assert(&i == &object1->i;
assert(42 == object2->i);
assert(&i == &object2->i);
assert(87.0 == object1->d);
assert(87.0 == object2->d);

Тип Выведенная область видимости
T unique
T& ошибка — должен быть связан как external
const T& unique (временный)
T* unique (передача владения)
const T* unique (передача владения)
T&& unique
unique_ptr unique
shared_ptr singleton
weak_ptr singleton

Уникальная область видимости Тест
auto injector = di::make_injector(    
    di::bind<i1, impl1>.in(di::unique)
);     

assert(injector.create<shared_ptr<i1>>()
       !=
       injector.create<shared_ptr<i1>>()
     | );

Разделяемая область видимости (в пределах потока) Тест
auto injector = di::make_injector(    
    di::bind<i1, impl1>.in(di::shared)
);     

assert(injector.create<shared_ptr<i1>>()
       ==
       injector.create<shared_ptr<i1>>()
     | );

Синглтон (разделяем между потоками) Тест
auto injector = di::make_injector(      
   di::bind<i1, impl1>.in(di::singleton)
);       

assert(injector.create<shared_ptr<i1>>()
       ==
       injector.create<shared_ptr<i1>>());

Область видимости сессии Тест
auto my_session = []{};           
   
auto injector = di::make_injector(
    di::bind<i1, impl1>.in(       
        di::session(my_session)   
    )                             
); 

assert(nullptr == injector.create<shared_ptr<i1>>());
injector.call(di::session_entry(my_session));
assert(injector.create<shared_ptr<i1>>()
       ==
       injector.create<shared_ptr<i1>>());
injector.call(di::session_exit(my_session));
assert(nullptr == injector.create<shared_ptr<i1>>());

Внешняя область видимости Тест
auto l = 42l;                          
auto b = false;                        
        
auto injector = di::make_injector(     
   di::bind<int, int_<41>>             
 , di::bind<int>.to(42)                
 , di::bind<i1>.to(make_shared<impl>())
 , di::bind<long>.to(ref(l))           
 , di::bind<short>.to([]{return 87;})  
 , di::bind<i2>.to(                    
     [&](const auto& injector)         
        -> shared_ptr<i2> {            
            if (b) {                   
              return injector.template 
                create<                
                  shared_ptr<impl2>>();
            }                          
            return nullptr;            
     }  
   )    
);      

assert(42 == injector.create<int>()); // external has priority
assert(injector.create<shared_ptr<i1>>()
       ==
       injector.create<shared_ptr<i1>>()
);
assert(l == injector.create<long&>());
assert(&l == &injector.create<long&>());
assert(87 == injector.create<short>());
{
auto object = injector.create<shared_ptr<i2>>();
assert(nullptr == object);
}
{
b = true;
auto object = injector.create<shared_ptr<i2>>();
assert(dynamic_cast<impl2*>(object.get()));
} 

Специальная область видимости Тест
struct custom_scope {                   
  static constexpr                      
      auto priority = false;            
         
  template<class TExpected, class>      
  struct scope {                        
    template<class T, class TProvider>  
    auto create(const TProvider& pr) {  
      return                            
        shared_ptr<TExpected>{pr.get()};
    }    
  };     
};       
         
auto injector = di::make_injector(      
  di::bind<i1, impl1>.in(custom_scope{})
);       

assert(injector.create<shared_ptr<i1>>()
       !=
       injector.create<shared_ptr<i1>>()
);

Boost.DI: внедрение зависимости в С++ - 2

Модули

Примеры

Модуль Тест
struct c {                           
    c(unique_ptr<i1> i1              
    , unique_ptr<i2> i2              
    , int i) : i1(move(i1))          
             , i2(move(i2)), i(i)    
    { }                              
      
    unique_ptr<i1> i1;               
    unique_ptr<i2> i2;               
    int i = 0;                       
};    
      
struct module1 {                     
    auto configure() const noexcept {
        return di::make_injector(    
            di::bind<i1, impl1>      
          , di::bind<int>.to(42)     
        );                           
    } 
};    
      
struct module2 {                     
    auto configure() const noexcept {
        return di::make_injector(    
            di::bind<i2, impl2>      
        );                           
    };                               
};    
      
auto injector = di::make_injector(   
    module1{}, module2{}             
);    

auto object = injector.create<unique_ptr<c>>();
assert(dynamic_cast<impl1*>(object->i1.get()));
assert(dynamic_cast<impl2*>(object->i2.get()));
assert(42 == object->i);
auto up1 = injector.create<unique_ptr<i1>>();
assert(dynamic_cast<impl1*>(up1.get()));
                    
auto up2 = injector.create<unique_ptr<i2>>();
assert(dynamic_cast<impl2*>(up2.get()));

Модуль, открывающий тип Тест
struct c {                          
    c(shared_ptr<i1> i1             
    , shared_ptr<i2> i2             
    , int i) : i1(i1), i2(i2), i(i) 
    { }                             
     
    shared_ptr<i1> i1;              
    shared_ptr<i2> i2;              
    int i = 0;                      
};   
     
struct module {                     
    di::injector<c> configure()     
    const noexcept;                 
     
    int i = 0;                      
};   
     
di::injector<c> // открывает c         
module::configure() const noexcept {
    return di::make_injector(       
        di::bind<i1, impl1>         
      , di::bind<i2, impl2>         
      , di::bind<int>.to(i)         
    );                              
}    
     
auto injector = di::make_injector(  
    module{42}                      
);

auto object = injector.create<c>();
assert(dynamic_cast<impl1*>(object.i1.get()));
assert(dynamic_cast<impl2*>(object.i2.get()));
assert(42 == object.i);
                       
// injector.create<unique_ptr<i1>>() // ошибка компиляции
// injector.create<unique_ptr<i2>>() // ошибка компиляции

Модуль, открывающий несколько типов Тест
struct module {                      
    di::injector<i1, i2> configure() 
    const noexcept;                  
      
    int i = 0;                       
};    
      
di::injector<i1, i2> // открывает i1, i2
module::configure() const noexcept { 
    return di::make_injector(        
        di::bind<i1, impl1>          
      , di::bind<i2, impl2>          
    );                               
}     
      
auto injector = di::make_injector(   
    module{}                         
);                    

auto up1 = injector.create<unique_ptr<i1>>();
assert(dynamic_cast<impl1*>(up1.get()));
                    
auto up2 = injector.create<unique_ptr<i2>>();
assert(dynamic_cast<impl2*>(up2.get()));

Модуль открытого типа с аннотацией Тест
auto my = []{};                        
        
struct c {                             
    BOOST_DI_INJECT(c                  
      , (named = my) unique_ptr<i1> up)
      : up(up)                         
    { } 
        
    unique_ptr<i1> up;                 
};      
        
di::injector<i1> module =              
    di::make_injector(                 
        di::bind<i1, impl1>            
    );  
        
auto injector = di::make_injector(     
    di::bind<i1>.named(my).to(module)  
);      

auto object = injector.create<unique_ptr<c>>();
assert(dynamic_cast<impl1*>(object->up.get()));

Провайдеры

Примеры

«no throw»-провайдер Тест
class heap_no_throw {                  
public: 
  template<                            
    class // interface                 
  , class T // implementation          
  , class TInit // direct()/uniform{}  
  , class TMemory // heap/stack        
  , class... TArgs>                    
  auto get(const TInit&                
         , const TMemory&              
         , TArgs&&... args)            
  const noexcept {                     
      return new (nothrow)             
        T{forward<TArgs>(args)...};    
  }     
};      
        
class my_provider : public di::config {
public: 
    auto provider() const noexcept {   
        return heap_no_throw{};        
    }   
};                             

// политика инжектора
auto injector = di::make_injector<my_provider>();
assert(0 == injector.create<int>());
// глобальная политика
#define BOOST_DI_CFG my_provider
auto injector = di::make_injector();
assert(0 == injector.create<int>());

Политики

Примеры
Ещё примеры

Определение политик конфигурации (дамп типов) Тест
class print_types_policy              
    : public di::config {             
public:                               
  auto policies() const noexcept {    
    return di::make_policies(         
      [](auto type){                  
         using T = decltype(type);    
         using arg = typename T::type;
         cout << typeid(arg).name()   
              << endl;                
      }                               
    ); 
  }    
};     

// политика инжектора
auto injector = di::make_injector<print_types_policy>();
injector.create<int>(); // вывод: int
// глобальная политика
#define BOOST_DI_CFG my_policy
auto injector = di::make_injector();
injector.create<int>(); // вывод: int

Определение политик конфигурации (развёрнутый дамп типов) Тест
class print_types_info_policy           
    : public di::config {               
public:  
  auto policies() const noexcept {      
    return di::make_policies(           
      [](auto type                      
       , auto dep                       
       , auto... ctor) {                
         using T = decltype(type);      
         using arg = typename T::type;  
         using arg_name =               
            typename T::name;           
         using D = decltype(dep);       
         using scope =                  
            typename D::scope;          
         using expected =               
            typename D::expected;       
         using given =                  
            typename D::given;          
         using name =                   
            typename D::name;           
         auto ctor_s = sizeof...(ctor); 
         
         cout << ctor_s                 
              << endl                   
              << typeid(arg).name()     
              << endl                   
              << typeid(arg_name).name()
              << endl                   
              << typeid(scope).name()   
              << endl                   
              << typeid(expected).name()
              << endl                   
              << typeid(given).name()   
              << endl                   
              << typeid(name).name()    
              << endl;                  
         ;                              
      }  
    );   
  }      
};       

// политика инжектора
auto injector = di::make_injector<print_types_info_policy>(
    di::bind<i1, impl1>
);
injector.create<unique_ptr<i1>>();
// вывод:
    0 // ctor_size of impl1
    unique_ptr<i1> // ctor arg
    di::no_name // ctor arg name
    di::deduce // scope
    i1 // expected
    impl1 // given
    no_name // dependency
// глобальная политика
#define BOOST_DI_CFG my_policy
auto injector = di::make_injector(
    di::bind<i1, impl1>
);
injector.create<unique_ptr<i1>>();
// вывод:
    0 // ctor_size of impl1
    unique_ptr<i1> // cotr arg
    di::no_name // ctor arg name
    di::deduce // scope
    i1 // expected
    impl1 // given
    no_name // dependency

Политика «может быть сконструирован» Тест
#include <boost/di/               
    policies/constructible.hpp>   
   
class all_must_be_bound_unless_int
    : public di::config {         
public:                           
  auto policies() const noexcept {
    using namespace di::policies; 
    using namespace               
        di::policies::operators;  
   
    return di::make_policies(     
      constructible(              
        is_same<_, int>{} ||      
        is_bound<_>{})            
    );                            
  }                               
}; 

// глобальная политика
#define BOOST_DI_CFG all_must_be_bound_unless_int
assert(0 == di::make_injector().create<int>());
// di::make_injector().create<double>(); // ошибка компиляции
assert(42.0 == make_injector(
                   di::bind<double>.to(42.0)
               ).create<double>()
);

Производительность на рантайме

Окружение

  • x86_64 Intel® Core(TM) i7-4770 CPU @ 3.40GHz GenuineIntel GNU/Linux
  • clang++3.4 -O2 / gdb -batch -ex 'file ./a.out' -ex 'disassemble main'
Создание типа без привязок Asm x86-64 (то же, что «return 0»)
int main() {                            
    auto injector = di::make_injector();
    return injector.create<int>();      
}        

xor %eax,%eax
retq

Создание типа с привязкой объекта Asm x86-64 (то же, что «return 42»)
int main() {                          
    auto injector = di::make_injector(
        di::bind<int>.to(42)          
    ); 
       
    return injector.create<int>();    
}      

mov $0x2a,%eax
retq

Создание именованного типа Asm x86-64 (то же, что «return 42»)
auto my_int = []{};                   
       
struct c {                            
    BOOST_DI_INJECT(c                 
        , (named = my_int) int i)     
        : i(i)                        
    { }                               
       
    int i = 0;                        
};     
       
int main() {                          
  auto injector = di::make_injector(  
    di::bind<int>.named(my_int).to(42)
  );   
       
  return injector.create<c>().i;      
}      

mov $0x2a,%eax
retq

Создание привязки интерфейса к реализации Asm x86-64 (то же, что «make_unique»)
int main() {                          
    auto injector = di::make_injector(
        di::bind<i1, impl1>           
    ); 
       
    auto ptr = injector.create<       
        unique_ptr<i1>                
    >();                              
       
    return ptr.get() != nullptr;      
}      

push   %rax
mov    $0x8,%edi
callq  0x4007b0 <_Znwm@plt>
movq   $0x400a30,(%rax)
mov    $0x8,%esi
mov    %rax,%rdi
callq  0x400960 <_ZdlPvm>
mov    $0x1,%eax
pop    %rdx
retq

Создание привязки интерфейса через модуль Asm x86-64 (то же, что «make_unique»)
struct module {                       
    auto configure() const noexcept { 
        return di::make_injector(     
            di::bind<i1, impl1>       
        );                            
    }  
};     
       
int main() {                          
    auto injector = di::make_injector(
        module{}                      
    ); 
       
    auto ptr = injector.create<       
        unique_ptr<i1>                
    >();                              
       
    return ptr != nullptr;            
}

push   %rax
mov    $0x8,%edi
callq  0x4007b0 <_Znwm@plt>
movq   $0x400a10,(%rax)
mov    $0x8,%esi
mov    %rax,%rdi
callq  0x400960 <_ZdlPvm>
mov    $0x1,%eax
pop    %rdx
retq

Создание привязки интерфейса через открытый модуль Asm x86-64
цена = вызов виртуального метода
struct module {                         
    di::injector<i1> configure() const {
        return di::make_injector(       
            di::bind<i1, impl1>         
        );                              
    }    
};       
         
int main() {                            
    auto injector = di::make_injector(  
        module{}                        
    );   
         
    auto ptr = injector.create<         
        unique_ptr<i1>                  
    >(); 
         
    return ptr != nullptr;              
}        

push   %rbp                               mov    (%rax),%ecx
push   %rbx                               lea    -0x1(%rcx),%edx
sub    $0x38,%rsp                         mov    %edx,(%rax)
lea    0x10(%rsp),%rdi                    cmp    $0x1,%ecx
lea    0x8(%rsp),%rsi                     jne    0x400bcd <main+173>
callq  0x400bf0 <_ZN5boost2di7exposed>    mov    (%rbx),%rax
mov    0x18(%rsp),%rdi                    mov    %rbx,%rdi
mov    (%rdi),%rax                        callq  *0x10(%rax)
lea    0x30(%rsp),%rsi                    lea    0xc(%rbx),%rax
callq  *0x10(%rax)                        mov    $0x0,%ecx
test   %rax,%rax                          test   %rcx,%rcx
setne  %bpl                               je     0x400bb8 <main+152>
je     0x400b57 <main+55>                 mov    $0xffffffff,%ecx
mov    (%rax),%rcx                        lock   xadd %ecx,(%rax)
mov    %rax,%rdi                          mov    %ecx,0x30(%rsp)
callq  *0x8(%rcx)                         mov    0x30(%rsp),%ecx
mov    0x20(%rsp),%rbx                    jmp    0x400bbf <main+159>
test   %rbx,%rbx                          mov    (%rax),%ecx
je     0x400bcd <main+173>                lea    -0x1(%rcx),%edx
lea    0x8(%rbx),%rax                     mov    %edx,(%rax)
mov    $0x0,%ecx                          cmp    $0x1,%ecx
test   %rcx,%rcx                          jne    0x400bcd <main+173>
je     0x400b82 <main+98>                 mov    (%rbx),%rax
mov    $0xffffffff,%ecx                   mov    %rbx,%rdi
lock   xadd %ecx,(%rax)                   callq  *0x18(%rax)
mov    %ecx,0x30(%rsp)                    movzbl %bpl,%eax
mov    0x30(%rsp),%ecx                    add    $0x38,%rsp
jmp    0x400b89 <main+105>                pop    %rbx
           pop    %rbp
-->        retq

Производительность на этапе компиляции

Пример

Окружение

  • x86_64 Intel® Core(TM) i7-4770 CPU @ 3.40GHz GenuineIntel GNU/Linux
  • clang++3.4 -O2
Заголовочный файл Boost.DI Время [сек]
#include <boost/di.hpp> 
int main() { }          

0.165

Легенда

  • ctor = «голый» конструктор: c(int i, double d);
  • inject = внедрение в конструктор: BOOST_DI_INJECT(c, int i, double d);
  • all = все типы в модуле доступны: auto configure();
  • exposed = один тип в модуле доступен: di::injector<c> configure();

Boost.DI: внедрение зависимости в С++ - 3

  • 4248897537 объектов создано
  • 132 разных типа
  • 10 модулей

Boost.DI: внедрение зависимости в С++ - 4

  • 1862039751439806464 объекта создано
  • 200 разных типов
  • 10 модулей

Boost.DI: внедрение зависимости в С++ - 5

  • 5874638529236910091 объект создан
  • 310 разных типов
  • 100 разных интерфейсов
  • 10 модулей

Диагностические сообщения

Создание интерфейса без привязки к реализации Сообщение об ошибке
auto injector = di::make_injector();
injector.create<unique_ptr<i1>>();  

error: allocating an object of abstract                  
class type 'i1' return new (nothrow)
T{forward<TArgs>(args)...};

Неоднозначность привязки Сообщение об ошибке
auto injector = di::make_injector(
    di::bind<int>.to(42)          
  , di::bind<int>.to(87)          
);
injector.create<int>(); 

error: base class 'pair<int, no_name>'
specified more than once as a direct
base class

Создание объекта без привязок
при политике, требующей их
Сообщение об ошибке
class all_bound : public di::config {
public: 
  auto policies() const noexcept {
    return di::make_policies(
      constructible(is_bound<_>{}) 
    );
  } 
};

auto injector =  di::make_injector<all_bound>();
injector.create<int>();

error: static_assert failed                           
"Type T is not allowed"

Неверный синтаксис аннотаций
(NAMED вместо named)
Сообщение об ошибке
auto name = []{};                
  
struct c {                       
    BOOST_DI_INJECT(c            
        , (NAMED = name) int) { }
};                               
  
di::make_injector().create<c>(); 

error: use of undeclared identifier
'named'

Конфигурация

Макрос Описание
BOOST_DI_CFG_CTOR_LIMIT_SIZE            
         
----------------------------------------
BOOST_DI_CFG                            
         
----------------------------------------
BOOST_DI_INJECTOR                       
         

Лимит на максимальное количество разрешенных 
параметров конструктора [0-10, по-умолчанию 10]
----------------------------------------
Глобальная конфигурация, позволяющая 
переопределять провайдеры и политики
----------------------------------------
Имя, используемое внутри Boost.DI для определения 
свойств конструкторов
[по-умолчанию boost_di_injector__]

Похожие библиотеки

Лицензия: Boost Software License, Version 1.0
Нюанс: библиотека пока не входит в Boost, а лишь находится в "инкубаторе".

Автор: tangro

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js