📜 ⬆️ ⬇️

Boost.DI: dependency injection in C ++

“Do not call us. We will call you yourself. "- the principle of Hollywood

Dependency injection (DI) means the transfer (deployment) of one or more dependencies to an object (client, component) in such a way that, after implementation, this dependence becomes part of the object state. This is a bit like the design strategy pattern, with the only difference that the strategy is set only once - in the constructor. DI allows you to create a more loosely-connected application architecture that is better supported and tested.

So, once again the pros:

')
Without dependency injectionWith the introduction of addiction
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 is a library for implementing dependency injection in C ++. To use it, you only need to include one header file in your code. The purpose of the library is to simplify the creation of objects using automatic dependency injection:


Manual dependency injectionBoost.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(); } 


Once again - why use dependency injection




image



Where to begin?





In general, all you need to work with the library is this di.hpp file .

  // main.cpp #include "di.hpp" int main() { } 


  $CXX -std=c++1y -I. main.cpp 


Let's assume that all the examples below include boost / di.hpp and define di namespace as alias 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 { } }; 


Injector


Creating an empty injectorTest
 auto injector = di::make_injector(); 

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


Bindings


Examples
More examples
Interface binding to implementationTest
 auto injector = di::make_injector(di::bind<i1, impl1>); 

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


Binding multiple interfaces to one implementationTest
 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())); 


Type binding to the value calculated at compile timeTest
 template<int N> using int_ = integral_constant<int, N>; auto injector = di::make_injector(di::bind<int, int_<42>>); 

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


Type binding to valueTest
 auto injector = di::make_injector(di::bind<int>.to(42)); 

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


Implementation


Examples
More examples
Implementation through the usual constructorTest
 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); 


Implementing through aggregationTest
 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); 


Implementation through the designer if available
several constructors (the one with
the largest number of parameters)
Test
 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); 


Implementation through the designer in the presence of options
selection (using BOOST_DI_INJECT)
Test
 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); 


Implementation through the designer in the presence of options
selection (using BOOST_DI_INJECT_TRAITS)
Test
 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); 


Implementation through the designer in the presence of options
choice (using di :: ctor_traits)
Test
 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); 


Annotations


Examples
Implementation through annotated constructorsTest
 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); 


Implementation through annotated constructors with
using named parameters
Test
 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" == cs); 


Implementation through annotated constructors with
making implementation of the constructor
Test
 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); 


Implementation through annotated constructors with
using di :: ctor_traits
Test
 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); 


Scopes


Examples
More examples

Display scope (default)Test
 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); 


Type ofDerived scope
Tunique
T &error - must be linked as external
const T &unique (temporary)
T *unique (transfer of ownership)
const T *unique (transfer of ownership)
T &&unique
unique_ptrunique
shared_ptrsingleton
weak_ptrsingleton

Unique scopeTest
 auto injector = di::make_injector( di::bind<i1, impl1>.in(di::unique) ); 

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


Shared visibility (within the stream)Test
 auto injector = di::make_injector( di::bind<i1, impl1>.in(di::shared) ); 

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


Singleton (shared between streams)Test
 auto injector = di::make_injector( di::bind<i1, impl1>.in(di::singleton) ); 

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


Scope of sessionTest
 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>>()); 


Outer scopeTest
 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())); } 


Special scopeTest
 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>>() ); 




Modules


Examples
ModuleTest
 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())); 


Module opening typeTest
 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>>() //   


The module that opens several typesTest
 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())); 


Open type module with annotationTest
 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())); 


Providers


Examples
"No throw" providerTest
 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>()); 


Politicians


Examples
More examples
Defining configuration policies (dump types)Test
 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 


Defining configuration policies (detailed type dump)Test
 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 


"Can be constructed" policyTest
 #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>() ); 


Runtime performance


Environment


Creating a type without bindingsAsm x86-64 (same as "return 0")
 int main() { auto injector = di::make_injector(); return injector.create<int>(); } 

 xor %eax,%eax retq 


Creating a type with object bindingAsm x86-64 (same as "return 42")
 int main() { auto injector = di::make_injector( di::bind<int>.to(42) ); return injector.create<int>(); } 

 mov $0x2a,%eax retq 


Create Named TypeAsm x86-64 (same as "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 


Creating an interface binding to the implementationAsm x86-64 (same as 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 


Creating an interface binding through a moduleAsm x86-64 (same as 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 


Creating an interface binding through an open moduleAsm x86-64
price = virtual method call
 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 


Performance at compile time


Example

Environment


Header file Boost.DITime [sec]
 #include <boost/di.hpp> int main() { } 

0.165

Legend











Diagnostic messages


Creating an interface without reference to the implementationError message
 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)...}; 


Binding ambiguityError message
 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 


Creating an object without bindings
with policies requiring them
Error message
 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" 


Invalid annotation syntax
(NAMED instead of named)
Error message
 auto name = []{}; struct c { BOOST_DI_INJECT(c , (NAMED = name) int) { } }; di::make_injector().create<c>(); 

 error: use of undeclared identifier 'named' 


Configuration


MacroDescription
 BOOST_DI_CFG_CTOR_LIMIT_SIZE ---------------------------------------- BOOST_DI_CFG ---------------------------------------- BOOST_DI_INJECTOR 

        [0-10, - 10] ----------------------------------------  ,      ---------------------------------------- ,   Boost.DI     [- boost_di_injector__] 


Related Libraries




License: Boost Software License, Version 1.0
Nuance: the library is not yet included in Boost, but is only in the " incubator ".

Source: https://habr.com/ru/post/248879/


All Articles