Our genuine love is C++. Unfortunately clients don't always share our favors, so we mostly occupied in the C#, java and javascript. Nevertheless, we're closely watching the evolution of the C++. It became more mature in the latest specs.
Recently, we wondered how would we deal with dependency injection in C++. What we found is only strengthened our commitment to C++.
Parameter packs introduced in C++ 11 allow trivial implementation of constructor injection, while std::type_index, std::type_info and std:any give service containers.
In fact there are many DI implementations out there. The one we refer here is Boost.DI. It's not standard nor we can claim it's the best but it's good example of how this concept can be implemented.
So, consider their example seen in Java with CDI, in C# in .NET Core injection, and in C++:
Java:
@Dependent public class Renderer { @Inject @Device private int device; }; @Dependent public class View { @Inject @Title private String title; @Inject private Renderer renderer; }; @Dependent public class Model {}; @Dependent public class Controller { @Inject private Model model; @Inject private View view; }; @Dependent public class User {}; @Dependent public class App { @Inject private Controller controller; @Inject private User user; }; ... Privider<App> provider = ... App app = provider.get();
C#:
public class RenderedOptions { public int Device { get; set; } } public class ViewOptions { public int Title { get; set; } } public class Renderer { public Renderer(IOptions<RendererOptions> options) { Device = options.Device; } public int Device { get; set; } } public class View { public View(IOptions<ViewOptions> options, Renderer renderer) { Title = options.Title; Renderer = renderer; } public string Title { get; set; } public Renderer Renderer { get; set; } } public class Model {} public class Controller { public Controller(Model model, View view) { Model = model; View = view; } public Model Model { get; set; } public View View { get; set; } }; public class User {}; public class App { public App(Controller controller, User user) { Controller = controller; User = user; } public Controller Controller { get; set; } public User User { get; set; } }; ... IServiceProvider serviceProvider = ... serviceProvider.GetService<App>();
C++:
#include <boost/di.hpp> namespace di = boost::di; struct renderer { int device; }; class view { public: view(std::string title, const renderer&) {} }; class model {}; class controller { public: controller(model&, view&) {} }; class user {}; class app { public: app(controller&, user&) {} }; int main() { /** * renderer renderer_; * view view_{"", renderer_}; * model model_; * controller controller_{model_, view_}; * user user_; * app app_{controller_, user_}; */ auto injector = di::make_injector(); injector.create<app>(); }
What is different between these DI flavors?
Not too much from the perspective of the final task achieved.
In java we used member injection, with qualifiers to inject scalars.
In C# we used constructor injection with Options pattern to inject scalars.
In C++ we used constructor injection with direct constants injected.
All technologies have their API to initialize DI container, but, again, while API is different, the idea is the same.
So, expressiveness of C++ matches to those of java and C#.
Deeper analysis shows that java's CDI is more feature rich than DI of C# and C++, but, personally, we consider it's advantage of C# and C++ that they have such a light DI.
At the same time there is an important difference between C++ vs java and C#.
While both java and C# are deemed to use reflection (C# in theory could use code generation on the fly to avoid reflection), C++'s DI natively constructs and injects services.
What does it mean for the user?
Well, a lot! Both in java and in C# you would not want to use DI in a performance critical part of code (e.g. in a tight loop), while it's Ok in C++ due to near to zero performance impact from DI. This may result in more modular and performant code in C++.