inversify-cpp
September 19, 2023 ยท View on GitHub
C++17 inversion of control and dependency injection container library.
Dependency Visualization
See the inversify-cpp-visualizer project to generate dependency graphs.
Features
- Constant, dynamic, and automatic resolvers
- Singleton, resolution (TODO), and unique scopes
Documentation
Installation
inversify-cpp supports conan. add inversify-cpp/2.1.0 to your conanfile.py.
view the library on conan center
Scope
Scope manages the uniqueness of a dependency.
Singleton scopes are cached after the first resolution and will be returned on subsequent container.get... calls.
Resolution scopes are cached throughout the duration of a single container.get... call. A dependency tree with duplicate dependencies will resolve each to the same cached value.
By default, the unique scope is used (except for constant values). The unique scope will resolve a unique dependency for each container.get... call.
Integration
#include <mosure/inversify.hpp>
// for convenience
namespace inversify = mosure::inversify;
Examples
Declare Interfaces
struct IFizz {
virtual ~IFizz() = default;
virtual void buzz() = 0;
};
using IFizzPtr = std::unique_ptr<IFizz>;
Declare Types
namespace symbols {
using foo = inversify::Symbol<int>;
using bar = inversify::Symbol<double>;
using fizz = inversify::Symbol<IFizzPtr>;
using fizzFactory = inversify::Symbol<std::function<IFizzPtr()>>;
using autoFizzUnique = inversify::Symbol<std::function<IFizzUniquePtr>>;
using autoFizzShared = inversify::Symbol<std::function<IFizzSharedPtr>>;
}
Note: symbols which hold the same interface type may do so via structs which inherit inversify::Symbol
Declare Classes and Dependencies
struct Fizz : IFizz {
Fizz(int foo, double bar)
:
foo_(foo),
bar_(bar)
{ }
void buzz() override {
std::cout << "Fizz::buzz() - foo: " << foo_
<< " - bar: " << bar_
<< " - counter: " << ++counter_
<< std::endl;
}
int foo_;
int bar_;
int counter_ { 0 };
};
template <>
struct inversify::Injectable<Fizz>
: inversify::Inject<
symbols::foo,
symbols::bar
>
{ };
Configure Bindings
inversify::Container<
symbols::foo,
symbols::bar
> container;
Constant Values
Constant bindings are always singletons.
container.bind<symbols::foo>().toConstantValue(10);
container.bind<symbols::bar>().toConstantValue(1.618);
Dynamic Bindings
Dynamic bindings are resolved when calling container.get....
By default, dynamic bindings have resolution scope (e.g. each call to container.get... calls the factory).
Singleton scope dynamic bindings cache the first resolution of the binding.
container.bind<symbols::fizz>().toDynamicValue(
[](auto& ctx) {
auto foo = ctx.container.template get<symbols::foo>();
auto bar = ctx.container.template get<symbols::bar>();
auto fizz = std::make_shared<Fizz>(foo, bar);
return fizz;
}
).inSingletonScope();
Factory Bindings
Dynamic bindings can be used to resolve factory functions.
container.bind<symbols::fizzFactory>().toDynamicValue(
[](auto& ctx) {
return [&]() {
auto foo = ctx.container.template get<symbols::foo>();
auto bar = ctx.container.template get<symbols::bar>();
auto fizz = std::make_shared<Fizz>(foo, bar);
return fizz;
};
}
);
Automatic Bindings
Dependencies can be resolved automatically using an automatic binding. Injectables are a prerequisite to the type being constructed.
Automatic bindings can generate instances, unique_ptr's, and shared_ptr's of a class. The returned type is determined by the binding interface.
container.bind<symbols::autoFizzUnique>().to<Fizz>();
container.bind<symbols::autoFizzShared>().to<Fizz>().inSingletonScope();
Resolving Dependencies
auto bar = container.get<symbols::bar>();
auto fizz = container.get<symbols::fizz>();
fizz->buzz();
auto fizzFactory = container.get<symbols::fizzFactory>();
auto anotherFizz = fizzFactory();
anotherFizz->buzz();
auto autoFizzUnique = container.get<symbols::autoFizzUnique>();
autoFizzUnique->buzz();
auto autoFizzShared = container.get<symbols::autoFizzShared>();
autoFizzShared->buzz();
Running Tests
Use the following to run tests:
bazel run test --enable_platform_specific_config
Note: run the example app in a similar way:
bazel run example/simple --enable_platform_specific_config
TODOS
- More compile-time checks
- Resolution scope
Profiling
Run the following to generate a callgrind file:
bazel run example/profiling --enable_platform_specific_config --compilation_mode=dbg -svalgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes ./bazel-bin/example/profiling/profiling
Containerless Version
See the containerless branch for a static binding (containerless) version of the library.
Generating single_include Variant
Run python ./third_party/amalgamate/amalgamate.py -c ./third_party/amalgamate/config.json -s ./ from the root of the repository.
Thanks
- InversifyJS - API design and inspiration for the project.