Dabbling Seriously

Code, Coffee, and other Stuff.

A miniature IOC Container in C++

Inversion of control containers allow systems to achieve dependency injection, by specifying the dependencies of objects indirectly. Typically the IOC container will be responsible for the actual instantiation of a concrete object, selecting a constructor and returning only the reference type, obtaining instances of any dependencies before calling the constructor. Today I’ve had a play at creating a minimal one of these and thought I would share it with you.

Obtaining a type code

Before we can safely store a way of obtaining a class subscribing to a particular interface, we should allocate a type code for this. In my implementation of the IOC container I have a static integer variable that specifies the next type id that will be allocated, and a static local variable instance for each type, which can be accessed by calling the GetTypeID method.

class IOCContainer
{
   static int s_nextTypeId;
public:

   //one typeid per type
   template<typename T>
   static int GetTypeID()
   {
      static int typeId = s_nextTypeId ++;
      return typeId;
   }

Obtaining object instances

Ok, that was easy. Now since we’ve got a type ID we should be able to store some kind of factory object to represent the fact that we don’t know how to create the object. Since I want to store all of the factories in the same collection, I’m opting for an abstract base class which the factories will derive from, and an implementation that captures a functor to call later.

For brevity, I’ve used a std::map to hold the factories, however you might consider other options for the sake of efficiency. If you’re building the collection once and then using it several times, then a sorted std::vector of std::pair would make a lot of sense.

   class FactoryRoot
   {
   public:
      virtual ~FactoryRoot() {}
   };
   
   //todo: consider sorted vector
   std::map<int, std::shared_ptr<FactoryRoot>> m_factories;
   
   template<typename T>
   class CFactory: public FactoryRoot
   {
      std::function<std::shared_ptr<T> ()> m_functor;
   public:
      ~CFactory() {}
      
      CFactory(std::function<std::shared_ptr<T> ()> functor)
         :m_functor(functor)
      {
      }
      
      std::shared_ptr<T> GetObject()
      {
         return m_functor();
      }
   };
   
   template<typename T>
   std::shared_ptr<T> GetObject()
   {
      auto typeId = GetTypeID<T>();
      auto factoryBase = m_factories[typeId];
      auto factory = std::static_pointer_cast<CFactory<T>>(factoryBase);
      return factory->GetObject();
   }

Registering Instances

Now all we need to do is populate the collection. I’ve implemented a few different ways of doing this: You might want to supply a functor explicitly, or you might want to choose between a single instance, or creating new instances on demand.

   //Most basic implementation - register a functor
   template<typename TInterface, typename ...TS>
   void RegisterFunctor(std::function<std::shared_ptr<TInterface> (std::shared_ptr<TS> ...ts)> functor)
   {
      m_factories[GetTypeID<TInterface>()] = std::make_shared<CFactory<TInterface>>([=]{return functor(GetObject<TS>()...);});
   }

   //Register one instance of an object
   template<typename TInterface>
   void RegisterInstance(std::shared_ptr<TInterface> t)
   {
      m_factories[GetTypeID<TInterface>()] = std::make_shared<CFactory<TInterface>>([=]{return t;});
   }

   //Supply a function pointer
   template<typename TInterface, typename ...TS>
   void RegisterFunctor(std::shared_ptr<TInterface> (*functor)(std::shared_ptr<TS> ...ts))
   {
      RegisterFunctor(std::function<std::shared_ptr<TInterface> (std::shared_ptr<TS> ...ts)>(functor));
   }
   
   //A factory that will call the constructor, per instance required
   template<typename TInterface, typename TConcrete, typename ...TArguments>
   void RegisterFactory()
   {
      RegisterFunctor(
         std::function<std::shared_ptr<TInterface> (std::shared_ptr<TArguments> ...ts)>(
            [](std::shared_ptr<TArguments>...arguments) -> std::shared_ptr<TInterface>
            {
               return std::make_shared<TConcrete>(std::forward<std::shared_ptr<TArguments>>(arguments)...);
            }));
   }
   
   //A factory that will return one instance for every request
   template<typename TInterface, typename TConcrete, typename ...TArguments>
   void RegisterInstance()
   {
      RegisterInstance<TInterface>(std::make_shared<TConcrete>(GetObject<TArguments>()...));
   }
};

Usage Scenario

Ok, we’re pretty much ready to go. Here I’m going to show a toy usage scenario, where I’ve registered two objects – one with a constructor that will be called and pass in the instance of the other type. You’ll note that I have to define the static type id counter here, and give it an initial value – it’s not important what value you use here, but I just wanted it to be something that is easy to recognise.

IOCContainer gContainer;

//initialise with nonzero number
int IOCContainer::s_nextTypeId = 115094801;

class IAmAThing
{
public:
   virtual ~IAmAThing() { }
   virtual void TestThis() = 0;
};

class IAmTheOtherThing
{
public:
   virtual ~IAmTheOtherThing() { }
   virtual void TheOtherTest() = 0;
};

class TheThing: public IAmAThing
{
public:
   TheThing()
   {
   }
   void TestThis()
   {
      std::cout << "A Thing" << std::endl;
   }
};

class TheOtherThing: public IAmTheOtherThing
{
   std::shared_ptr<IAmAThing> m_thing;
public:
   TheOtherThing(std::shared_ptr<IAmAThing> thing):m_thing(thing)
   {
   }
   void TheOtherTest()
   {
      m_thing->TestThis();
   }
};

int main(int argc, const char * argv[])
{
   gContainer.RegisterInstance<IAmAThing, TheThing>();
   gContainer.RegisterFactory<IAmTheOtherThing, TheOtherThing, IAmAThing>();
   gContainer.GetObject<IAmTheOtherThing>()->TheOtherTest();
   
   return 0;
}

Final bits

So that’s pretty much it for this time. I think there’s potentially several things that you could do to improve the container, but this is a start. If you have any comments, suggestions, or complaints let me know! I’d love to hear from you. Thanks for reading.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on September 13, 2015 by in c++, CodeProject, coding, IOC, programming and tagged , , , .
%d bloggers like this: