I made a single-header, non-intrusive IoC Container in C++17
[https://github.com/SirusDoma/Genode.IoC](https://github.com/SirusDoma/Genode.IoC)
A non-intrusive, single-header IoC container for C++17.
I was inspired after stumbling across a compiler loophole I found [here](https://alexpolt.github.io/type-loophole.html). Just now, I rewrote the whole thing without relying on that loophole because I just found out that my game sometimes won't compile on clang macOS without some workarounds.
Anyway, this is a similar concept to Java Spring, or C# Generic Host / Autofac, but unlike [kangaru](https://github.com/gracicot/kangaru) or other IoC libraries, this one is single header-only and most importantly: **non-intrusive**. Meaning you don't have to add anything extra to your classes, and it just works.
I have used this previously to develop a [serious game](https://www.reddit.com/r/gamedev/comments/1rldvqa/i_remade_an_online_retro_game_by_reverse/) with complex dependency trees (although it uses a previous version of this library, please check that link, it's made with C++ too), and a game work-in-progress that I'm currently working on with the new version I just pushed.
Template programming is arcane magic to me, so if you found something flawed / can be improved, please let me know and go easy on me 😅
**EDIT**
As requested, let me briefly talk about what IoC is:
IoC container stands for [Inversion of Control](https://en.wikipedia.org/wiki/Inversion_of_control), as mentioned, a similar concept to [Spring in Java](https://docs.spring.io/spring-framework/reference/core/beans/introduction.html). By extension, it is a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) pattern that manages and abstracts dependencies in your code.
Imagine you have the following classes in your app:
struct NetworkSystem
{
NetworkSystem(Config& c, Logger& l, Timer& t, Profiler* p)
: config(&c), logger(&l), timer(&t), profiler(&p) {}
Config* config; Logger* logger; Timer* timer; Profiler *profiler;
};
In a plain old-school way, you initialize the `NetworkSystem` by doing this:
auto config = Config(fileName);
auto logger = StdOutLogger();
auto timer = Timer();
auto profiler = RealProfiler(someInternalEngine, someDependency, etc);
auto networkSystem = NetworkSystem(config, logger, timer, profiler);
And you have to manage the lifetime of these components individually. With IoC, you could do something like this:
auto ioc = Gx::Context(); // using my lib as example
// Using custom init
// All classes that require config in their constructor will be using this config instance as long as they are created via this "ioc" object.
ioc.Provide<Config>([] (auto& ctx) {
return std::make_unique<Config>(fileName);
});
// Usually you have to tell container which concrete class to use if the constructor parameter relies on abstract class
// For example, Logger is an abstract class and you want to use StdOut
ioc.Provide<Logger, StdOutLogger>();
// Now simply call this to create network system
networkSystem = ioc.Require<NetworkSystem>(); // will create NetworkSystem, all dependencies created automatically inside the container, and it will use StdOutLogger
That's the gist of it. Most of the IoC container implementations are customizable, meaning you can control the construction of your class object if needed and automate the rest.
Also, the lifetime of the objects is tied to the IoC container; this means if the container is destroyed, all objects are destroyed (typically with some exceptions; in my lib, using `Instantiate<T>` returns a `std::unique_ptr<T>`). On top of that, depending on the implementation, some libraries provide sophisticated ways to manage the lifetime.
I would suggest familiarizing yourself with the IoC pattern before trying it out to avoid anti-patterns: For example, passing the container itself to the constructor is considered an anti-pattern. The following code
Post #24825
15