diff --git a/.bazelrc b/.bazelrc index fd0eaaa..f30718e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,3 @@ -build:windows --copt="/std:c++17" --copt="/W4" +build:windows --copt="/std:c++17" --copt="/W4" --copt="/O2" build:linux --copt="-std=c++17" --copt="-O3" -build:macos --copt="-std=c++17" +build:macos --copt="-std=c++17" --copt="-O3" diff --git a/.gitignore b/.gitignore index 6dd515f..915ed92 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ .vscode/ bazel-* + +callgrind.out.* diff --git a/BUILD b/BUILD index c693788..0cfbf49 100644 --- a/BUILD +++ b/BUILD @@ -23,6 +23,7 @@ cc_library( "-Wall", "-Wextra", "-Wshadow", + "-Wunused", ], hdrs = glob([ "include/**/*.hpp", diff --git a/README.md b/README.md index a78f5fb..f7bd657 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ C++17 inversion of control and dependency injection container library. +## Containerless Version + +See the [`containerless` branch](https://github.com/mosure/inversify-cpp/tree/containerless) for a static binding (containerless) version of the library. + ## Features * Constant, dynamic, and automatic resolvers * Singleton, resolution (TODO), and unique scopes @@ -71,6 +75,8 @@ namespace symbols { ``` +> Note: symbols which hold the same interface type may do so via structs which inherit inversify::Symbol + #### Declare Classes and Dependencies ```cpp @@ -135,9 +141,9 @@ Singleton scope dynamic bindings cache the first resolution of the binding. ```cpp container.bind().toDynamicValue( - [](const inversify::Context& ctx) { - auto foo = ctx.container.get(); - auto bar = ctx.container.get(); + [](auto& ctx) { + auto foo = ctx.container.template get(); + auto bar = ctx.container.template get(); auto fizz = std::make_shared(foo, bar); @@ -154,10 +160,10 @@ Dynamic bindings can be used to resolve factory functions. ```cpp container.bind().toDynamicValue( - [](const inversify::Context& ctx) { + [](auto& ctx) { return [&]() { - auto foo = ctx.container.get(); - auto bar = ctx.container.get(); + auto foo = ctx.container.template get(); + auto bar = ctx.container.template get(); auto fizz = std::make_shared(foo, bar); @@ -209,12 +215,19 @@ 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 --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 -s` +* `valgrind --tool=callgrind --dump-instr=yes --simulate-cache=yes --collect-jumps=yes ./bazel-bin/example/profiling/profiling` + ## Generating `single_include` Variant Run `python ./third_party/amalgamate/amalgamate.py -c ./third_party/amalgamate/config.json -s ./` from the root of the repository. diff --git a/example/profiling/BUILD b/example/profiling/BUILD new file mode 100644 index 0000000..04b43b3 --- /dev/null +++ b/example/profiling/BUILD @@ -0,0 +1,13 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") + + +cc_binary( + name = "profiling", + srcs = [ + "main.cpp", + ], + deps = [ + "//:inversify", + ], + visibility = ["//visibility:public"] +) diff --git a/example/profiling/main.cpp b/example/profiling/main.cpp new file mode 100644 index 0000000..f39f6b9 --- /dev/null +++ b/example/profiling/main.cpp @@ -0,0 +1,46 @@ +#include + +#include + + +namespace inversify = mosure::inversify; + +using int_symbol = inversify::Symbol; + +struct Service { + int val; + + Service() : val(0) { } + Service(int val) : val(val) { } + + void foo() { + std::cout << "hello: " << val << std::endl; + } +}; + +template <> +struct inversify::Injectable + : inversify::Inject< + int_symbol + > +{ }; + +using test_symbol = inversify::Symbol; + + +template +void to_profile(Container& container) { + container.template get().foo(); +} + +int main() { + inversify::Container< + int_symbol, + test_symbol + > container; + + container.bind().toConstantValue(3); + container.bind().to(); + + to_profile(container); +} diff --git a/example/BUILD b/example/simple/BUILD similarity index 74% rename from example/BUILD rename to example/simple/BUILD index 2e4692a..baa57df 100644 --- a/example/BUILD +++ b/example/simple/BUILD @@ -2,7 +2,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") cc_library( - name = "example_api", + name = "simple_api", hdrs = glob([ "api/*.hpp", "src/*.hpp", @@ -14,7 +14,7 @@ cc_library( ) cc_library( - name = "example_src_hdrs", + name = "simple_src_hdrs", hdrs = glob([ "src/*.hpp", ]), @@ -25,13 +25,13 @@ cc_library( ) cc_binary( - name = "example", + name = "simple", srcs = glob([ "src/*.cpp", ]), deps = [ - ":example_api", - ":example_src_hdrs", + ":simple_api", + ":simple_src_hdrs", "//:inversify", ], visibility = ["//visibility:public"] diff --git a/example/api/logger.hpp b/example/simple/api/logger.hpp similarity index 100% rename from example/api/logger.hpp rename to example/simple/api/logger.hpp diff --git a/example/api/service.hpp b/example/simple/api/service.hpp similarity index 100% rename from example/api/service.hpp rename to example/simple/api/service.hpp diff --git a/example/api/settings.hpp b/example/simple/api/settings.hpp similarity index 100% rename from example/api/settings.hpp rename to example/simple/api/settings.hpp diff --git a/example/api/symbols.hpp b/example/simple/api/symbols.hpp similarity index 100% rename from example/api/symbols.hpp rename to example/simple/api/symbols.hpp diff --git a/example/src/logger.hpp b/example/simple/src/logger.hpp similarity index 100% rename from example/src/logger.hpp rename to example/simple/src/logger.hpp diff --git a/example/src/main.cpp b/example/simple/src/main.cpp similarity index 79% rename from example/src/main.cpp rename to example/simple/src/main.cpp index 19034f5..d5747f4 100644 --- a/example/src/main.cpp +++ b/example/simple/src/main.cpp @@ -11,7 +11,11 @@ namespace inversify = mosure::inversify; int main() { - inversify::Container container; + inversify::Container< + symbols::logger, + symbols::service, + symbols::settings + > container; container.bind().to().inSingletonScope(); container.bind().to(); diff --git a/example/src/mock_logger.hpp b/example/simple/src/mock_logger.hpp similarity index 100% rename from example/src/mock_logger.hpp rename to example/simple/src/mock_logger.hpp diff --git a/example/src/service.hpp b/example/simple/src/service.hpp similarity index 100% rename from example/src/service.hpp rename to example/simple/src/service.hpp diff --git a/example/src/settings.hpp b/example/simple/src/settings.hpp similarity index 100% rename from example/src/settings.hpp rename to example/simple/src/settings.hpp diff --git a/include/mosure/binding.hpp b/include/mosure/binding.hpp index 2c68aff..870c1ff 100644 --- a/include/mosure/binding.hpp +++ b/include/mosure/binding.hpp @@ -1,56 +1,73 @@ #pragma once #include -#include +#include #include #include #include +#include namespace mosure::inversify { - template + template class BindingScope { public: void inSingletonScope() { - resolver_ = std::make_shared>(resolver_); + static_assert( + std::is_copy_constructible::value, + "inversify::BindingScope singleton must have copy constructor" + ); + + this->factory_ = [this, factory = std::move(factory_)](auto& context) { + if (!this->cached_set_) { + this->cached_ = factory(context); + this->cached_set_ = true; + } + + return this->cached_; + }; } protected: - inversify::ResolverPtr resolver_; + T cached_; + bool cached_set_ { false }; + inversify::Factory factory_; }; - template - class BindingTo : public BindingScope { + template + class BindingTo + : public BindingScope + { public: void toConstantValue(T&& value) { - this->resolver_ = std::make_shared>(value); + this->factory_ = [val = std::move(value)](auto&) { + return val; + }; } - BindingScope& toDynamicValue(inversify::Factory factory) { - this->resolver_ = std::make_shared>(factory); + BindingScope& toDynamicValue(inversify::Factory&& factory) { + this->factory_ = std::move(factory); return *this; } template - BindingScope& to() { - this->resolver_ = std::make_shared>(); + BindingScope& to() { + this->factory_ = inversify::get_auto_resolver(); return *this; } }; - template - class Binding : public BindingTo { + template + class Binding + : public BindingTo + { public: - inline T resolve(const Context& context) const { - if (!this->resolver_) { - throw inversify::exceptions::ResolutionException("inversify::Resolver not found. Malformed binding."); - } - - return this->resolver_->resolve(context); + inline typename T::value resolve(const inversify::Context& context) { + return this->factory_(context); } }; diff --git a/include/mosure/container.hpp b/include/mosure/container.hpp index 8bd165d..a234377 100644 --- a/include/mosure/container.hpp +++ b/include/mosure/container.hpp @@ -1,31 +1,64 @@ #pragma once +#include + #include #include #include +#include namespace mosure::inversify { - template - struct BindingLookup { - inline static inversify::Binding binding {}; - }; - - class Container : public inversify::IContainer { + template + class Container + : public inversify::IContainer + { public: + static_assert( + meta::valid_symbol_types_v, + "inversify::Container symbols must be of type inversify::Symbol" + ); + + static_assert( + !meta::is_empty_v, + "inversify::Container must register at least one symbol" + ); + + using BindingMap = std::tuple< + inversify::Binding< + SymbolTypes, + SymbolTypes... + >... + >; + template - inline inversify::BindingTo& bind() { - return BindingLookup::binding; + inline inversify::BindingTo& bind() { + static_assert( + meta::contains_v, + "inversify::Container symbol not registered" + ); + + return std::get< + inversify::Binding + >(bindings_); } template - inline typename T::value get() const { - return BindingLookup::binding.resolve(context_); + inline typename T::value get() { + static_assert( + meta::contains_v, + "inversify::Container symbol not registered" + ); + + return std::get< + inversify::Binding + >(bindings_).resolve(context_); } private: - inversify::Context context_ { *this }; + BindingMap bindings_ {}; + inversify::Context context_ { *this }; }; } diff --git a/include/mosure/context.hpp b/include/mosure/context.hpp index beba74c..f6ba081 100644 --- a/include/mosure/context.hpp +++ b/include/mosure/context.hpp @@ -5,10 +5,12 @@ namespace mosure::inversify { + template class Container; + template struct Context { - const inversify::IContainer& container; + inversify::IContainer& container; }; } diff --git a/include/mosure/factory.hpp b/include/mosure/factory.hpp index ec98d4c..5e1d4a3 100644 --- a/include/mosure/factory.hpp +++ b/include/mosure/factory.hpp @@ -7,7 +7,7 @@ namespace mosure::inversify { - template - using Factory = std::function; + template + using Factory = std::function&)>; } diff --git a/include/mosure/injectable.hpp b/include/mosure/injectable.hpp index b71c32d..aa4ce7e 100644 --- a/include/mosure/injectable.hpp +++ b/include/mosure/injectable.hpp @@ -5,21 +5,24 @@ #include #include -#include namespace mosure::inversify { template struct Inject { - static_assert(meta::valid_inject_types_v, "inversify::Injectable dependencies must be of type inversify::Symbol"); - - inline static auto resolve(const inversify::Context& context) { - return std::make_tuple(context.container.get()...); + static_assert( + meta::valid_symbol_types_v, + "inversify::Injectable dependencies must be of type inversify::Symbol" + ); + + template + inline static auto resolve(const inversify::Context& context) { + return std::make_tuple(context.container.template get()...); } }; template > - struct Injectable : Inject { }; + struct Injectable: Inject { }; } diff --git a/include/mosure/interfaces/icontainer.hpp b/include/mosure/interfaces/icontainer.hpp index 10bdb3c..7cedf40 100644 --- a/include/mosure/interfaces/icontainer.hpp +++ b/include/mosure/interfaces/icontainer.hpp @@ -7,22 +7,28 @@ namespace mosure::inversify { - template + template < + typename T, + typename... SymbolTypes + > class BindingTo; - template + template < + template class Implementation, + typename... SymbolTypes + > class IContainer { public: template - inversify::BindingTo& bind() { - auto crtpImplementation = static_cast(this); + inversify::BindingTo& bind() { + auto crtpImplementation = static_cast *>(this); return crtpImplementation->template bind(); } template - typename T::value get() const { - auto crtpImplementation = static_cast(this); + typename T::value get() { + auto crtpImplementation = static_cast *>(this); return crtpImplementation->template get(); } diff --git a/include/mosure/inversify.hpp b/include/mosure/inversify.hpp index c21eb2b..0f8bc4b 100644 --- a/include/mosure/inversify.hpp +++ b/include/mosure/inversify.hpp @@ -33,7 +33,7 @@ SOFTWARE. #include // runtime_error #include // string #include // make_from_tuple, tuple -#include // apply, conjunction_v, false_type, true_type +#include // apply, conjunction_v, disjunction_v, false_type, is_copy_constructable, is_same, true_type #include #include diff --git a/include/mosure/meta.hpp b/include/mosure/meta.hpp index a6b9801..fab12ad 100644 --- a/include/mosure/meta.hpp +++ b/include/mosure/meta.hpp @@ -1,23 +1,67 @@ #pragma once +#include #include #include +#include + namespace mosure::inversify::meta { - template class Template> + template< + typename T, + typename... Types + > + inline constexpr bool contains_v = std::disjunction_v< + std::is_same... + >; + + + template + struct is_empty : std::false_type { }; + + template<> + struct is_empty<> : std::true_type { }; + + template + inline constexpr bool is_empty_v = is_empty::value; + + + template < + typename T, + template typename Template + > struct is_specialization : std::false_type { }; - template