C++ Service Container (Dependency Injection)

During my everyday web developer life, I use Service Containers and Dependency Injection (in PHP) tirelessly. These containers are useful to keep only one instance of each class.

So I thought I could create something similar in C++. I'm not sure if it would be something I would use in my own programs since I try to avoid singletons by passing down objects as needed and a proper architecture. But if the real case usage isn't clear at this moment, it will still be something fun to make.

The idea would be to have a ServiceContainer class containing a map of Instances of the classes we want to keep.

If the services instances are given in the right order, the ServiceContainer will use its own map to initialize new services.

The first step is to create a class that will contain and own an object:

class AbstractInstanceContainer
{
public:
    AbstractInstanceContainer() = default;
    virtual ~AbstractInstanceContainer() = default;

    AbstractInstanceContainer& operator=(AbstractInstanceContainer&& other) noexcept
    {
        mRawPointer = other.mRawPointer;
        other.mRawPointer = nullptr;

        return *this;
    }

    void* get() { return mRawPointer; }

protected:
    explicit AbstractInstanceContainer(void* ptr) : mRawPointer(ptr) {}

private:
    void* mRawPointer = nullptr;
};


template <class T>
class InstanceContainer : public AbstractInstanceContainer
{
public:
    explicit InstanceContainer(std::unique_ptr<T> ptr) : AbstractInstanceContainer(ptr.get()), mPointer(std::move(ptr)) {}
    ~InstanceContainer() override = default;

private:
    std::unique_ptr<T> mPointer;
};

Then we can define the actual ServiceContainer that will use templating thingamagic to create instances and increment a counter for each type of object we store so that they can be retrieved later in the map.

class ServiceContainer
{
public:

    template <typename T, typename... Deps, typename... Args>
    void set(Args... args)
    {
        auto instance = std::make_unique<T>(get<typename std::remove_const<Deps>::type>()..., args...);
        std::unique_ptr<InstanceContainer<T>> service = std::make_unique<InstanceContainer<T>>(std::move(instance));
        mContainer[typeId<T>()] = std::move(service);
    }

    template <typename T>
    T* get()
    {
        auto it = mContainer.find(typeId<T>());
        if (it == mContainer.end()) {
            throw ServiceContainerException(std::string("Service '" + std::string(typeid(T).name()) + "' not registered in container."));
        }

        return static_cast<T*>(it->second->get());
    }

private:
    template <typename T>
    int typeId()
    {
        static int id = ++mLastTypeId;

        return id;
    }

private:
    static int mLastTypeId;
    std::map<int, std::unique_ptr<AbstractInstanceContainer>> mContainer;
};

int ServiceContainer::mLastTypeId = 0;

And that's it! It's actually pretty simple, once the brainhache of template definition has passed. Time to test:

struct Service1
{
    int alpha = 0;

    ~Service1() {
        std::cout << "Service 1 deleted" << std::endl;
    }
};


struct Service2
{
    explicit Service2(Service1* s1) : s1(s1) {}

    int value() { return s1->alpha; }

    Service1* s1;

    ~Service2() {
        std::cout << "Service 2 deleted" << std::endl;
    }
};

struct Service3
{
    Service3(Service2* s2, Service1* s1)
    {

    }
};

int main ()
{

    {
        ServiceContainer serviceContainer;

        serviceContainer.set();
        try {
            serviceContainer.set();
        } catch (ServiceContainerException& e) {
            std::cerr << e.message << std::endl;
        }
        serviceContainer.set(3);
        serviceContainer.set();

        auto s1 = serviceContainer.get();
        std::cout << s1->alpha << std::endl;
        s1->alpha = 42;
        std::cout << s1->alpha << std::endl;


        auto s2 = serviceContainer.get();
        std::cout << s2->value() << std::endl;
        s1->alpha = 36;
        std::cout << s2->value() << std::endl;
        std::cout << serviceContainer.get()->alpha << std::endl;
    }

    return 0;
}