Dependency Injection in C++ – cppdepinject

While I’ve seen plenty about Dependency Injection and IOC containers in C# and Java, I recently had a reason to use one in C++. There are some existing IOC containers in C++ but they are not widely used. While there are differences in the types of software written in C++ compared to other languages I think the largest difference is the way the community works. The Java and C# communities grew up with the Internet and open-source code and expect to use code and ideas from various sources and the style of their applications has changed rapidly. The C++ community spends a lot more time than the other communities discussing core language issues and maybe this is one reason the style of C++ applications has not changed as much over the last decade so dependency injection hasn’t gained the same widespread acceptance.

So I thought I would write one at home as a simple coding kata. I thought building my own would help me better understand the problems and issues around the design of other IOC containers. My main aim was to explore just how flexible I could make it.

Designing the Interface

My initial minimal interface:

class Repository
{
public:
	template <typename T>
	std::shared_ptr<T> resolve() const;

	template <typename T>
	void registerInstanceObject(std::shared_ptr<T> singleInstance);
};
class Base
{
public:
	virtual ~Base() = 0  {}
};
class Concrete : public Base
{
};
TEST(CppInjectTest, resolve_AfterRegister_AlwaysReturnsObject) {// using Google C++ Testing Framework
	Repository r;
	std::shared_ptr<Base> o(new Concrete);
	r.registerInstanceObject<Base>(o);

	ASSERT_TRUE(o == r.resolve<Base>());
	ASSERT_TRUE(o == r.resolve<Base>());
}

I’ve already made some decisions:-

  • a concrete repository class that applications could use, rather than an interface that applications would have to each implement
  • a single repository class that handles all types, rather than a separate repository for each type i.e. repository.resolve<A>() and not Repository<A>.resolve()
  • one or multiple repository instances can be created as needed to remove unnecessary coupling between separate parts of an application
  • names register/resolve influenced by reading about Unity recently

This already decouples:-

  • creation of objects from use of objects
  • interface type from concrete type

But I’d like to handle objects with separate instances as well as service objects with only a single instance:

class Repository
{
	// using Visual C++ 2010 so function, shared_ptr originally in boost now available in std namespace
	template <typename T>
	void registerType(std::function<std::shared_ptr<T> ()> creator);
};
TEST(CppInjectTest, resolve_AfterRegisterType_ReturnsDifferentObjectEachTime) {
	Repository r;
	// using Visual C++ 2010 so lambdas can be used to simply create functors
	r.registerType<Base>([] { return shared_ptr<Base>(new Concrete); });
	ASSERT_TRUE(r.resolve<Base>() != r.resolve<Base>());
}

And I’d like to be able to register a type more than once for different uses:

class Repository
{
	template <typename T>
	std::shared_ptr<T> resolveByName(const std::string& name) const;

	template <typename T>
	void registerInstanceObject(std::shared_ptr<T> singleInstance, const std::string& name);
};
TEST(CppInjectTest, resolveByName_AfterRegisterWithName_ReturnsCorrectObject) {
	Repository r;
	r.registerInstanceObject<Base>(std::shared_ptr<Base>(new Concrete), "Prime");

	ASSERT_TRUE(r.resolveByName<Base>("Prime"));
}

And having registered a type more than once I’d like to be able to get all of them at once:

class Repository
{
	template <typename T>
	std::map<std::string, std::shared_ptr<T>> resolveAll() const;
};
TEST(CppInjectTest, resolveAll_WithTwoRegistrationa_ReturnsBoth) {
	Repository r;
	r.registerInstanceObject<Base>(std::shared_ptr<Base>(new Concrete1), "Default");
	r.registerType<Base>([] { return shared_ptr<Base>(new Concrete2); }, "Standard");
	// using Visual C++ 2010 so can use auto to deduce type of variable
	auto all(r.resolveAll<Base>());
	ASSERT_EQ(2, all.size());
	ASSERT_TRUE(all["Default"]);
	ASSERT_TRUE(all["Standard"]);
}

And how about allowing application to use name to create groups of type registrations:

class Repository
{
	template <typename T>
	std::map<std::string, std::shared_ptr<T>> resolveAll(std::function<bool (const std::string&)> filter) const;
};
TEST(CppInjectTest, resolveAllWithFilter_WithMatchingAndNonMatching_ReturnsOnlyMatching) {
	Repository r;
	r.registerInstanceObject<Base>(std::shared_ptr<Base>(new NullConcrete), "Default");
	r.registerInstanceObject<Base>([] { return shared_ptr<Base>(new Concrete("A"); }, "Standard/A");
	r.registerInstanceObject<Base>([] { return shared_ptr<Base>(new Concrete("B"); }, "Standard/B");
	auto all(r.resolveAll<Base>([] (const std::string& name) { return name.substr(0,9)=="Standard/"));
	ASSERT_EQ(2, all.size());
	ASSERT_TRUE(all["Standard/A"]);
	ASSERT_TRUE(all["Standard/B"]);
}

This is quite flexible but so far misses the real point of IOC containers – creating a complex object graph with objects that rely on
other registered objects:

class A  {};
class B
{
	std::shared_ptr<A> a;
public:
	B(std::shared_ptr<A> a) : a(a)  {}
	std::shared_ptr<A> getA()  {  return a;  }
};
TEST(CppInjectTest, resolve_AfterRegisterExtraDependency_ReturnsCorrectlyBuiltObject) {
	Repository r;
	r.registerInstanceObject<A>(std::shared_ptr<Base>(new A));
	r.registerInstanceObject<B>(std::shared_ptr<Base>(new B(r.resolve<A>()));
	ASSERT_TRUE(r.resolve<B>());
	ASSERT_TRUE(r.resolve<B>()->getA());
}

This works but requires the objects to be registered in just the right order which is fine if they are all created in one place but hard in practice and impossible if different modules register their own types and have dependencies on each other.

So let’s allow an instance to be registered with a creator function, so it will be created the first time it’s resolved and return the same object after that:

class Repository
{
	template <typename T>
	void registerInstance(std::function<std::shared_ptr<T> (const Repository&)> creator);
};
TEST(CppInjectTest, resolve_AfterRegisterExtraDependencyOutOfOrder_ReturnsCorrectlyBuiltObject) {
	Repository r;
	// register instance of type B but don't create it until resolved
	r.registerInstance<B>([] (const Repository&) { return shared_ptr<B>(new B(r.resolve<A>())); });
	// register type A needed to create type B
	r.registerInstance<A>(std::shared_ptr<Base>(new A));

	ASSERT_TRUE(r.resolve<B>());
	ASSERT_TRUE(r.resolve<B>()->getA());
}

And although the lambdas make things quite quick how about a descriptive shorthand for simple creator functions:

template <typename T>
struct SimpleCreator : std::unary_function<Repository, std::shared_ptr<T>>
{
	std::shared_ptr<T> operator()(const Repository&)
	{
		return std::shared_ptr<T>(new T);
	}
};
template <typename T, typename TP1>
struct SimpleCreator_OneResolved : std::unary_function<Repository, std::shared_ptr<T>>
{
	std::shared_ptr<T> operator()(const Repository& r)
	{
		return std::shared_ptr<T>(new T(
			r.resolve<TP1>()
			));
	}
};
TEST(CppInjectTest, resolve_AfterRegisterTypeWithSimpleCreator_ReturnsCorreclyBuiltObject) {
	Repository r;
	// register instance of type B that will get an A from repository
	r.registerInstance<B>(SimpleCreator_OneResolved<B, A>());
	r.registerInstance<A>(SimpleCreator<A>());

	ASSERT_TRUE(r.resolve<B>());
	ASSERT_TRUE(r.resolve<B>()->getA());
}

Summarize the Design Ideas

Interface:

  • separate object creation from object use
  • how objects constructed and how many other dependencies they may have are separated both from code using objects and from the repository
  • decouple interface type at use from concrete type at creation
  • types registered need no changes for repository (optionally take dependencies as shared_ptr to allow use of simple template creators)
  • allow either single instances or type-factories to be make discoverable, set when registered, no difference at resolve time
  • registration naming scheme separate from registered types
  • templates for standard creator functions or simple to write custom creators for a new type or a specific registration
  • static set of registered types rather than dynamically changing during application life i.e. types all registered up-front
  • allow restricted lookup by name or filter
  • allow listing of all registered items for a type
  • classes know nothing about repository (optionally take dependencies as shared_ptr to allow use of simple template creators)
  • allow application to have higher order structure through chosen registration naming scheme

Repository:

  • a concrete repository class that applications could use, rather than an interface that applications would have to each implement
  • a single repository class that handles all types, rather than a separate repository for each type
  • one or many repository instances can be created as needed to remove unnecessary coupling between separate parts of an application

Use in C++:

  • header file only for simple use in any application
  • use of shared_ptr throughout to avoid lifetime issues

Issues:-

  • possibility of circular dependencies leading to memory exhaustion as shared_ptr will lead to neither object being destroyed
  • possibly should allow Repository to be injected into created objects
  • possibly should allow registration name to be something other than a string for more complex naming schemes

I don’t seem to have said anything about the implementation. That’s basically because it is less important than having an easy to use interface that imposes no demands on the types being registered. This is only a light object repository to help decouple your code, it’s not meant to be a framework that forces you to write your code a certain way or that is even visible from most of your application code. Just a little bit of the plumbing.

For my current needs this is working well and I’ve open-sourced it – UPDATED NOW AT https://github.com/cbgibbs/cppdepinject

No Comments

Leave a Reply

Your email address will not be published. Required fields are marked *