A look at what is dependency injection, and how to implement constructor injection in C++ for member variables of a class, using interfaces with virtual functions.

Introduction

I first saw dependency injection used systematically in AngularJS, a Javascript framework. However the technique comes from Java and this example illustrates how it would apply to C++.

Let’s try to implement the example from the previous article in such a way so that we can write tests for house class.

Code

This is how the house class can change. It receives its dependencies in the constructor. The relevant member variables become references that get initialized in the constructor with the values passed from the outside. Note that you might choose to not inject all member variables (e.g. an int, or a standard container member variable that is part of the logic of that class).

house.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "cuppa_interface.h"
#include "door_interface.h"
#include "tv_interface.h"

class house
{
public:
  house(
    cuppa_interface & cuppa,
    door_interface & door,
    tv_interface & tv);

public:
  void chillax();

private:
  cuppa_interface & cuppa_;
  door_interface & door_;
  tv_interface & tv_;
};

house.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "house.h"

house::house(
    cuppa_interface & cuppa,
    door_interface & door,
    tv_interface & tv) :
      cuppa_{ cuppa },
      door_{ door },
      tv_{ tv }
{
}

void house::chillax() {
  cuppa_.finish();

  door_.open();
  door_.close();

  tv_.switch_on();
}

Here’s how for example the cuppa class can change. First we need to define an interface of pure virtual functions.

cuppa_interface.h

1
2
3
4
5
6
7
struct cuppa_interface
{
  virtual void finish() = 0;

protected:
  ~cuppa_interface() {};
};

The virtual destructor in the interface helps with the issue of someone taking a pointer to the interface and deleting it. A protected destructor stops that from happening. The other option would be to have a public virtual destructor.

Then the cuppa class implements this interface.

cuppa.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "cuppa_interface.h"

#include "cup.h"
#include "tea.h"

class cuppa :
  public cuppa_interface
{
public:
  void finish() override;

private:
  tea tea_;
  cup cup_;
};

cuppa.cpp

1
2
3
4
5
6
#include "cuppa.h"

void cuppa::finish() {
  tea_.drink();
  cup_.rinse();
}

We can now test the house class, using gtest and gmock for example.

house_test.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <gtest.h>
#include <gmock.h>

#include "house.h"

class cuppa_mock :
  public cuppa_interface
{
public:
  MOCK_METHOD(void, finish, (), (override));
};

// ... `door_mock` and `tv_mock` ...

TEST(house_test, trivial)
{
  cuppa_mock c;
  EXPECT_CALL(c, finish())
    .Times(1);
  door_mock d;
  tv_mock t;

  house h{ c, d, t };
  h.chillax();
}

We are left now with the issue that somewhere we need to instantiate all the objects.

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "cuppa.h"
#include "door.h"
#include "tv.h"

#include "house.h"

int main() {
  cuppa c;
  door d;
  tv t;

  house h{ c, d, t };
  h.chillax();
}

Discussion

Was it worth it? In the fictional example above, probably not. We went from 24 lines of code in 2 files for the house class to I recon in this example about 70 lines required to implement and support it.

The advantages are:

  • It makes it trivial to unit test in isolation classes that are in the middle of the application logic. For this alone I’m willing to forego its problems (until a better option becomes feasible).
  • Constructor injection makes it obviously (compiler checks it) which dependencies need to be created to instantiate a class. That is better than using a setter method to inject dependencies.

The disadvantages are:

  • A small runtime cost from the usage of the virtual functions. For a certain class of applications this can be ignored.
  • More code and additional files. This is mitigated by the fact that the code is simple, and can be described in rules.
  • Risc of low value tests. For a simple class like the above one can infer from reading the code in the chillax function as to what’s going to happen. The mitigation is: then don’t write unit tests for such simple functionality.
  • When there are multiple types implementing the same interface, debugging is harder. This is mitigated by the fact that the ability to unit test more classes, leads to less issues to debug in the first place.
  • This style also introduces the need for places where components are put together like the main function above, or a factory-like class.
  • It’s not easy to create objects of a type. You need to inject a factory class which adds to the code complexity.

Conclusion

Using some form of dependency injection makes it trivial to write unit tests for any class of your choosing. It is a style that most developers from a traditional OOP background will find it easy to understand.

However I expect that in the future the concepts and modules for templates will make a template based approach a better method to do dependency injection.

References

For ideas on using dependency injection at construction time in Java see The Clean Code Talks - “Global State and Singletons” by Miško Hevery (13 Nov 2008).