Search code examples
c#c++memory-managementcominversion-of-control

Is DI possible without a managed heap?


With dependency injection, a class' dependency is instantiated by the caller and passed in, often as a constructor argument. This works well in languages with a managed heap, since there is no need to worry about the end of the dependency's lifespan. But what about other types of languages?

For example, in a traditional malloc and free environment, a method that allocates memory generally should also release it. I am not sure how that would be accomplished with DI.

Or with a memory scheme that requires reference counting, e.g. COM, I am not sure when the caller would call Release on the dependency, or if the object that receives the injection should call Release twice.

Is it possible to use DI without a managed heap? If so, what code patterns work well to ensure that resources are released correctly?


Solution

  • But what about other types of languages? Is it possible to use DI without a managed heap?

    Having a managed heap is not a prerequisite for DI. C++ for example is not a managed language, yet there are DI frameworks for it, comparable in features with DI frameworks for managed languages like Java or C#.

    Daniele Pallastrelli's excellent presentation Going native with less coupling - Dependency Injection in C++ explains in detail the benefits of DI over two other decoupling techniques (factories and service locators). It also presents a C++ DI framework called Wallaroo and explains its internals.

    Another one C++ DI framework, based on a different approach then Wallaroo is [Boost].DI. I highly recommend reading the Introduction chapter. It gives short but good answers to questions like "Do I use a Dependency Injection already?", "Do I need a Dependency Injection?", etc.

    The third C++ DI framework I want to mention is the Infector++.

    These are just three of many C++ DI frameworks out there. You can find plenty of them listed on this page.

    My point is, if there are so many DI frameworks for C++, no matter if they are widely accepted or not, it is surely possible to have DI without a managed heap :-)

    If so, what code patterns work well to ensure that resources are released correctly?

    The links above provide additional input on how a complete DI framework can be done in C++ including dependency resolution, different creation policies and object scopes and finally, your question, the object lifecycle management.

    Here I'll just sketch a general idea on how lifecycle management can be consistently and deterministically done. All of the mentioned frameworks heavily use smart pointers (std::unique_ptr, std::shared_ptr, also the boost::shared_ptr if they provide Boost support) and attach creation policy semantics to them. Note here that you do not need a full blown DI framework to use this pattern. The basic idea is very simple.

    Suppose I declare a class like the following one:

    class i_depend_on_others {
        i_depend_on_others(std::unique_ptr<other>,
                           std::shared_ptr<another_other>,
                           boost::shared_ptr<yet_another_other>)
        { }
    };
    

    This is a clear constructor injection, but with additional semantic about the expected lifetime of the "others". The first other will be owned by the i_depend_on_others instance and since we have the std::unique_ptr it will be deleted as soon as the i_depend_on_others instance is deleted. The another_other and yet_another_other are expected to have lifecycle independent of the i_depend_on_others instance. This pattern clearly define when is the i_depend_on_others instance responsible for cleaning the resource and when the calling code should do it. (In the case of DI framework, the framework takes care of the shared instances.)

    The question is what to do in this case:

    class i_depend_on_others_as_well {
        i_depend_on_others_as_well(other*) { }
    };
    

    (I'll skip arguing here that raw pointers should be avoided in modern C++ development. Let's say we are forced to use them.) Again, the pattern defines clear semantics. Raw pointer implies ownership transfer. The instance of the i_depend_on_others_as_well is responsible for deleting the other.

    In case of DI frameworks like [Boost].DI, the type of the pointer will dictate the default lifecycle of the injected objects. For share pointers, they will be singeltons, created once and maintained by the [Boost].DI, and for raw pointers and unique pointers, a new instance will be created each time.

    A more detailed explanation of this pattern can be found in the "Decide the life times" chapter of the [Boost].DI documentation.