Dependency Injection in C++

In my day job as a C# developer, using dependency injection and IOC containers is an important part of the design of the applications we write. Almost everything is written against an interface which affords some flexibility, loose coupling and makes code easier to test.

I’m wondering if this a common approach when writing in C++? I have to think it might be since it is a language that supports OOP after all.

You can try doing it in your own code, but usually when you are using larger code frameworks like Juce or Qt, you will be involving yourself a lot with the framework classes. (And sometimes they will involve nasty things like static or global variables…Juce is pretty good about that, though, because it is aimed a lot towards audio plugin development where statics and globals are a big no-no.) Those larger frameworks also often do not have a lot of “interfaces”, that is C++ classes which only have pure virtual methods.

Static and global variables are frowned upon in C++ too I see :smile:. So, the Juce framework makes it difficult to work this way but if you were writing something from the bottom up you would prefer to do it?

If I was doing a framework from scratch, I probably wouldn’t prefer writing a lot of “interfaces”, because I would just get users complaining the framework doesn’t do enough for them. Overall, Juce seems to be pretty good in that regard, the classes that simply need to do a lot, do a lot, and the classes that don’t need to do a lot, don’t do a lot.

1 Like

These days in C++, if a class needs some behavior customization point, a std::function member often works well and doesn’t require additional classes or dependencies. C++ also has function templates which can be used for injecting dependencies in some situations.

As an example of using std::function, here’s a Component that paints itself using std::function :

class DrawClass : public Component
{
public:
	DrawClass() {}
	void paint(Graphics& g) override
	{
		if (DrawFunction) // check the callback function has been set
			DrawFunction(g,getWidth(),getHeight());
	}
	std::function<void(Graphics&, int,int)> DrawFunction;
};

Then another class can use it with something like :

    m_draw_a.DrawFunction = [](Graphics& g, int w, int h)
	{
		g.setColour(Colours::white);
		g.drawLine(0, 0, w, h);
	};
	m_draw_b.DrawFunction = [](Graphics& g, int w, int h)
	{
		g.setColour(Colours::yellow);
		g.fillEllipse(0, 0, w, h);
	};
    m_message = "Hello!";
	m_draw_c.DrawFunction = [this](Graphics& g, int w, int h)
	{
		g.setColour(Colours::white);
		g.drawText(this->m_message, 0, 0, w, h, Justification::centred);
	};
	

So, in that case, no separate new classes are needed to draw the white diagonal line, the yellow filled ellipse or the text message. m_draw_a, m_draw_b and draw_c are all DrawClass instances, potentially reducing class dependencies. Especially the draw_c case is interesting in terms of reducing class coupling because it captures information from the current class instance (“this”) in scope. The DrawClass doesn’t need to know anything about the other class involved.

2 Likes

Nice :wink: You always give me great advice and avenues to investigate.

I’m also a C# developer and a TDD guy. I’m sure there exists IOC’s for C++ but i think you don’t need it for a plugin or other small JUCE projects. For the few testable classes you will write, you can do Inversion Of Control by yourself without an IO Container. Most plugin and UI code is strongly coupled to the framework and the implementation and it does not make sense to test it in my opinion. I’m also not sure if it makes sense to test the DSP code.
It’s not possible to bootstrap the whole JUCE framework with an IOC as far as i know.

A few years ago I was working on a C++11 project and found Google Juice too complicated, I had done alot of work with C# Ninject so ended up writing my own C++11 header only Dependency Injector - in all honesty it works really really well.

Check it out at

note, it is called CPPServiceLocator due to it evolution, it was originally a ServiceLocator and the guts of it still is, but when used with SLModules which are intimate with both SLContext and the classes they construct it becomes a Dependency Injection system, if you use runtime configuration to control the loading of SLModules then it becomes very powerful :slight_smile:

2 Likes

Another dev here that did a lot of C# before coming over to C++ via JUCE. The other main thing I think people who’ve made this transition might be looking for is a design pattern for achieving MVVM (more commonly known as MVC in other languages).

The pattern I wound up going with injects references to std::function via the constructor of the views. For a simpler view with just a handful, you can inject them all individually, but more a more complex one, you can encapsulate them all in a struct. So you wind up with something like this:

class View : public Component
{
public:
View(std::function<void()>& doSmthRef) 
: doSomething(doSmthRef)
{
    addAndMakeVisible(button);
    button.onClick = [this] {
        if (doSomething)
            doSomething();
    };
}
private:
std::function<void()>& doSomething;
juce::Button button;
};

class Controller
{
public:
Controller() : view(doSomething) {

}
private: 
std::function<void()> doSomething;
View view;
};

I remember this being a lot more awkward with WinForms with the Code-Behind but became pretty elegant IMO with WPF. Did enough with MVVM and WPF that it became the only thing that made sense to me, so inevitably I tried to find a way to do the equivalent with JUCE and C++.

I don’t recommend storing a reference to a std::function, your class should take the function object by value to avoid lifetime issues.

For example, you could do:

class View : public Component
{
public:
View(std::function<void()> doSmth) 
{
    addAndMakeVisible(button);
    button.onClick = std::move(doSmth);
}
private:
juce::Button button;
};

Ah but then it is not as close of a match for the MVVM pattern, the goal being that the View has no specialized knowledge of what’s going on behind the scenes - only the raw data types (that will be supplied in the std::function’s signature that need to be bound to the ViewModel).

A reference is taken since the method must be executed outside of the View. If the lifecycle of the function is over then it will not pass the if (doSmth)
check and there will be no problems. This was the original goal of the C#/WPF designers, to achieve total decoupling of the View from the ViewModel. The ViewModel can also be fully decoupled from the Model via Dependency Injection.

If only. References in C++ can dangle.

That said, maybe I have something new to learn here, I’ll have to try out your alteration to the design pattern and try to reason about what is happening here.

In a way, the method continues to be executed “outside” of the View, but the View just takes ownership of the lifecycle.

This is something there’s no equivalent to in C# as a managed progamming language (other than manually invoking Garbage Collection rather than waiting for it to do a pass itself).

Found this which seems to confirm that the std::move approach can indeed provide the best of both worlds. It seems to suggest that “where the method” runs in terms of how C#'s MVVM pattern is organized is all kind of an illusion once you look under the hood of the managed memory model anyway.

That is actually just a code presentation problem. So if that’s the only problem, then you do get the best of both worlds using std::move since the method will indeed execute “outside of the View” in terms of stepping through it in the Controller class in the Debugger, but in terms of the actual memory model, the method is executing in the correct place as well.

Thanks @benvining

.. Just realizing now though, wouldn’t this wind up enforcing a 1-to-1 binding of a View Component to an underlying method?

You couldn’t have in this case multiple views all binding GUI objects to the same underlying method, because once you’ve std::move’d it once, isn’t that it?

I’ll have to try out a few scenarios and bring them back to this thread if I find the problem I’m expecting might occur:

2 totally different Views with two juce::Button, and you want to bind both button’s onClick lambdas to the same underlying doSmth method in the Controller.. can you still do that if you try to std::move the method into both View instances?

I’m not really sure what you mean by “where the method executes”. In general, the call stack is a different concept from where an object lives in memory or what its ownership semantics are.

You’re right that you shouldn’t move from an object twice. However, the View constructor can take the function object by value, which would create a copy. For example:

class View : public Component
{
public:
View(std::function<void()> doSmth) // takes function by value, calling this constructor creates a copy
{
    addAndMakeVisible(button);
    button.onClick = std::move(doSmth); // move from the copy sent to the constructor 
}
private:
juce::Button button;
};

// usage:
std::function<void()> callback { []{ /* ... */ } };

View a { callback };
View b { callback };

Ah so that could be a problem then depending on whether you are maintaining state with the model. How will you deal with two copies of the method executing simultaneously against a stateful model in that scenario?

I still think the approach of taking a reference is valid, because you will be able to maintain state against the method being executed.

This only works if the lifecycle of the Controller is guaranteed to be longer than the View, but that’s how it always is. The View opens and closes, the Model serializes and deserializes, but the Controller sticks around either for the entire application lifecycle, or at least longer than the Views it is bound to.

The std::function object being a value vs a reference doesn’t make a difference as far as concurrency / race conditions are concerned. The function can just as easily be invoked simultaneously through 2 references or through 2 copies.

In practice, in JUCE (and most other UI frameworks), all UI operations are on a single thread (the message thread), so all operations involving UI objects & callbacks are sequential and never simultaneous.

Not really sure what this means?

Your answer showed that you should indeed be able to maintain state (of a data model) against the method being executed so long as you can count on the sequential execution of the callbacks.

Coming back to where I was going with the discussion, it’s just something that a programmer coming from managed programming to unmanaged may need to wrap their head around - they may think that a method “belongs” to the Controller and not the View, but that paradigm really only makes sense in a managed programming context.

In unmanaged, lifecycle management takes precedence and by copying the function by value you can be sure that you won’t have those types of problems.