4

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?

John Wu
  • 50,556
  • 8
  • 44
  • 80
  • Yes, you can simply destroy the injected object when the class instance is destroyed. – Kerrek SB Feb 04 '17 at 22:48
  • In both managed and unmanaged environments, it is the [Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot/) that is in control over the creation and deterministic clean-up of resources. In unmanaged environments, this simply means a little bit more work, but it is still the Composition Root that is both responsible for creation of those components and freeing up their memory. – Steven Feb 05 '17 at 08:58

2 Answers2

1

For me DI and garbage collection are unrelated. You just need to organize memory management. Start from trying to use static objects everywhere. This will work if your DI graph is static (i.e. it is not changing during the work of the application).

If your DI graph is evolving or its structure is decided during the application startup, more info in needed on how you are doing this. I am not sure general solution that fits every case is possible. Entity that modifies the DI graph should be responsible for the cleanup. Exactly in the same way as it is done in C++ in other cases. For example you can place your DI proxy objects into a static container that will be destroyed (and if everything is properly organized its contents also) at the end of the work of the application.

In C/C++ memory management is needed in this or that way to manage containers, connections and zillion other things. I do not see why DI is special. Garbage collection solves memory management (still having its own drawbacks) for everything that it is used for.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
1

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.

ironcev
  • 382
  • 3
  • 15