A mini IoC implementation with C++.
The implementation of this IoC is based on A Miniature IOC Container in C++.
Some necessary modifications have been made to the source code on the website, and here is the original License: The Code Project Open License (CPOL)
This mini IoC container provides basic adding and resolving abilities and the simplest dependency injection. It provides two lifetimes - singleton and transient. Singleton object will be created only once, while transient object will be constructed every time it is resolved. What's more, it can provide lazy initialization for singleton instances. 😁
Since everything is implemented with C++ template, only header files are located under the mioc/include
directory. A test file, Test.cpp
is placed under the src/
directory as an example.
To avoid pointer problems, std::shared_ptr
is used everywhere to wrap all native pointers. 🙂
It will be very nice for you to light up the 🌟. :)
Here are the classes we use in this demonstration. Interfaces are omitted as they are straightforward. We can see that B depends on A, and C depends on B. 🧐
class A : public IA
{
public:
A() = default;
};
class B : public IB
{
public:
B(const std::shared_ptr<IA>& a) :_a(a) {}
private:
std::shared_ptr<IA> _a;
};
class C : public IC
{
public:
C(const std::shared_ptr<IB>& b) : _b(b) {}
private:
std::shared_ptr<IB> _b;
};
All required header files are included in mioc.h
, simply include this header file, and you are ready to go!
You can create a container using ServiceContainer::New()
. In this case, you have to inject this container everywhere you need. You can choose whether to enable lazy initialization for this container on creation. You won't be able to change it later. By default, lazy initialization is enabled.
mioc::ServiceContainerPtr lazyContainer = mioc::ServiceContainer::New();
mioc::ServiceContainerPtr hungryContainer = mioc::ServiceContainer::New(false);
Or, you can use SingletonContainer
, which provides a global-scale singleton container. This container will not be created until the first time you get it. Also, on your first call, you can choose whether to enable lazy initialization for the global container.
// by default, the global container enables lazy initialization
mioc::ServiceContainerPtr container = mioc::SingletonContainer::GetContainer();
// or you can disable it on, and only on the first call
mioc::ServiceContainerPtr container = mioc::SingletonContainer::GetContainer(false);
The first is to use the type name only. You should provide its interface type and concrete type. And all dependency types if it has.
container->AddSingleton<IA, A>(); // without dependency
container->AddSingleton<IB, B, IA>(); // with dependencies
Also, you can directly add a pre-constructed instance to it. This way, you may need a pointer conversion first, which converts concrete type to its corresponding interface. You don't need to pass a type name in this case. And, of course, lazy initialization matters not in this way.
std::shared_ptr<IC> c = std::make_shared<C>(b);
container->AddSingleton(c);
It is simpler to add a transient object. We need to pass type names, and they will be appropriately resolved later.
container->AddTransient<IA, A>();
container->AddTransient<IB, B, IA>();
container->AddTransient<IC, C, IB>();
No matter how you add objects into the container, you can always resolve an instance with the interface type you provided on adding. nullptr
will be returned if the interface is not registered.
std::shared_ptr<IC> c = container->Resolve<IC>();
Though this mini IoC container can handle dependency injection, you have to specify all dependencies when you add anything manually. 🥲
This project uses doctest for unit testing.