Click here to Skip to main content
Click here to Skip to main content

An IOC Container using Variadic Template

By , 1 Apr 2013
Rate this:
Please Sign up or sign in to vote.

As you will see from some of my previous posts, along with developing in C++, I have also had a fair amount of experience of WPF and C#. Whilst working with these technologies, I have generally employed a Dependency Injection model rather than a Service Locator pattern, as I believe this tends to make dependencies more explicit. Whilst the same can be achieved with a Service Locator patterm, often it is an excuse for a global property bag that can be called upon in any part of the code, this almost always leads to dependencies being hidden. Whilst developing some projects in C++, I have on occasion slipped into the sloppy practice of using a Service Locator badly and it got me thinking about implementing an IOC container for dependency injection in C++, using Variadic Templates, not only as a means to solving a problem I had, but also as a way of looking into Variadic Templates under VS2012. These features of C++ 11 are only available in the November 2012 CTP VC++ download.

Service Locator and Dependency Injection

There has been so much written comparing the two, even to the point of declaring a Service Locator an anti-pattern, which I believe is a little harsh. As such, I am not going to write too much about it here, but point you to a few articles:

Make of them what you will

Variadic Templates in C++ 11

Anyone working in C or C++ will be aware of variadic functions; functions that can take a varargs, such as printf:

int printf ( const char * format, ... );

The C++ 11 standard extends this concept to templates, allowing templates to have a variable number of template parameters:

template <typename... Ts>
int safe_printf(const char* f, const Ts&... ts);

A good introduction to them, by Andrei Alexandrescu can be found here:

Variadic Templates are Funadic (Going Native 2012), Andreio Alexandrescu.

As we shall see, as I develop my idea for an IOC Container for Dependency Injection, variadic templates, along with lambda functions, are the ideal mechanism to implement what I am after.

My Dependency Problem

This example is a simplified example, but imagine I have a dependency graph a great deal bigger than the one I’m describing, also imagine that these classes are written a little better!. I have four classes as follows:

class One
{
public:
    One(){}
    virtual ~One(){}
    std::string getOutput() const {  return message_;  }
    std::string message_;
};

class Two
{
public:
    Two(){}
    virtual ~Two(){}
    std::string getOutput() const {  return message_;  }
    std::string message_;
};

class Three
{
public:
    Three(){}
    virtual ~Three(){}
    std::string getOutput() const {  return "IOC Created";  }
};

class DependentClass
{
public:
    DependentClass(OnePtr one, TwoPtr two, ThreePtr three) 
      : one_(one), two_(two), three_(three){}
    void output() const 
    { 
        std::cout << "done it" << std::endl; 
        std::cout << "one - " << one_->getOutput() << std::endl; 
        std::cout << "two - " << two_->getOutput() << std::endl; 
        std::cout << "three - " << three_->getOutput() << std::endl; 

    }
private:
    DependentClass(){}
    OnePtr one_;
    TwoPtr two_;
    ThreePtr three_;
};

It is quite clear from the code above that DependentClass is dependent on the other three classes.  In my scenario, I want the One class to be a singleton, I want the Two class to be declared outside any kind of factory method and the Three and DependentClass to be managed inside the IOC container. What does this mean in terms of code? Well imagine the code being something like this (well, actually exactly like this):

IOCContainer container;

container.RegisterSingletonClass<One>();
OnePtr one = container.GetInstance<One>();
one->message_ = "Singleton";

TwoPtr two(new Two());
two->message_ = "Registered Instance";
container.RegisterInstance<Two>(two);

container.RegisterClass<Three>();
container.RegisterClass<DependentClass, One, Two, Three>();

DependentClassPtr instance = container.GetInstance<DependentClass>();

instance->output();

I want to register a singleton instance of One against my IOC container, with the assurance that if an instance of One is used, it is always the same one.  I also want to register an instance of Two that will also be used when needed. In reality this functionality ends up being very similar, although it does have a slight semantic difference.

I then want to register a Three with no dependencies and DependentClass with the three dependencies that is does have.

You will also notice I am using Ptr classes here. These are just typedefs for smart pointers to the various classes:

typedef std::shared_ptr<One> OnePtr;
typedef std::shared_ptr<Two> TwoPtr;
typedef std::shared_ptr<Three> ThreePtr;
typedef std::shared_ptr<DependentClass> DependentClassPtr;

An IOC Container with Variadic Templates in C++

So now I have outlined what I am trying to achieve, lets have a look at the implementation of the IOCContainer.

class IOCContainer
{
private:
    class IHolder
    {
    public:
        virtual void noop(){}
    };

    template<class T>
    class Holder : public IHolder
    {
    public:
        std::shared_ptr<T> instance_;
    };

    std::map<std::string, std::function<void*()>> creatorMap_;
    std::map<std::string, std::shared_ptr<IHolder>> instanceMap_;

public:

    template <class T, typename... Ts>
    void RegisterSingletonClass()
    {
        std::shared_ptr<Holder<T>> holder(new Holder<T>());
        holder->instance_ = std::shared_ptr<T>(new T(GetInstance<Ts>()...));

        instanceMap_[typeid(T).name()] = holder;
    }

    template <class T>
    void RegisterInstance(std::shared_ptr<T> instance)
    {
        std::shared_ptr<Holder<T>> holder(new Holder<T>());
        holder->instance_ = instance;

        instanceMap_[typeid(T).name()] = holder;
    }

    template <class T, typename... Ts>
    void RegisterClass()
    {
        auto createType = [this]() -> T * {
            return new T(GetInstance<Ts>()...);
        };

        creatorMap_[typeid(T).name()] = createType;
    }

    template <class T>
    std::shared_ptr<T> GetInstance()
    {
        if(instanceMap_.find(typeid(T).name()) != instanceMap_.end())
        {
            std::shared_ptr<IHolder> iholder = instanceMap_[typeid(T).name()];

            Holder<T> * holder = dynamic_cast<Holder<T>*>(iholder.get());
            return holder->instance_;
        }
        else
        {
            return std::shared_ptr<T>(static_cast<T*>
                                       (creatorMap_[typeid(T).name()]()));
        }
    }

};

Lets go through what is an initial solution to this problem. I won’t pretend this is the most robust solution, but hopefully it will give you some idea of where to start, but also give a brief introduction to some features of C++ 11.

Firstly, I need a couple of collections to represent the registry, a collection of registered instances and singletons and a collection of creator functions:

std::map<std::string, std::function<void*()>> creatorMap_;
std::map<std::string, std::shared_ptr<IHolder>> instanceMap_;

These two collections use a couple of features that are part of the new C++ 11 standard: function objects and shared pointers. I won’t go into detail here as good references can be found elsewhere:

You will also notice a Holder interface and template class:

class IHolder
{
public:
    virtual void noop(){}
};

template<class T>
class Holder : public IHolder
{
public:
    std::shared_ptr<T> instance_;
};

I want the classes that are registered with the IOC Container to be independent of a specific interface, but unfortunately std library container require the contained class to be the same type. By providing an interface with a no-op method (can’t remember why this was necessary?) and a template class that implements that interface I can provide a useful wrapper for an instance of a class that can be held in a container, such that the class contained can really be anything.

You will also notice that the registered creator functions return a void pointer. I am not completely happy about this, it requires some internal casting that might not be the best C++ code, but solves the problem for me. Suggestions for better ways of doing this are very welcome.

So we now have the basic internal types and collections to represent the data contained in the registry, lets have a look at some of the methods on the IOC container.

Firstly the simplest method, for registering existing instances of a class:

template <class T>
void RegisterInstance(std::shared_ptr<T> instance)
{
    std::shared_ptr<Holder<T>> holder(new Holder<T>());
    holder->instance_ = instance;

    instanceMap_[typeid(T).name()] = holder;
}

This does nothing new to C++ 11. It simply creates a new shared pointer to the shared pointer instance (mmm!) and adds it to the registry using the name from the typeid as the key. This could easily be extended to allow for multiple named registrations as well.

Now lets have a look at the RegisterClass method, which uses a lot of C++ 11 features:

template <class T, typename... Ts>
void RegisterClass()
{
    auto createType = [this]() -> T * {
        return new T(GetInstance<Ts>()...);
    };

    creatorMap_[typeid(T).name()] = createType;
}

Firstly we have a Variadic Template method that can take a variable number of template parameters. This allows classes to be registered with dependencies on 0 or more other types. Our creator functions are then declared as a lambda function createType, with an inferred type using the auto keyword. The creator method expands the template parameters using the GetInstance<>() method to create a new instance of T.

The RegisterSingleton method is similar:

template <class T, typename... Ts>
void RegisterSingletonClass()
{
    std::shared_ptr<Holder<T>> holder(new Holder<T>());
    holder->instance_ = std::shared_ptr<T>(new T(GetInstance<Ts>()...));

    instanceMap_[typeid(T).name()] = holder;
}

Although rather than adding a creator function, we just create an instance directly and add it to the registry of instances.

All that leaves now is the GetInstance method:

template <class T>
std::shared_ptr<T> GetInstance()
{
    if(instanceMap_.find(typeid(T).name()) != instanceMap_.end())
    {
        std::shared_ptr<IHolder> iholder = instanceMap_[typeid(T).name()];

        Holder<T> * holder = dynamic_cast<Holder<T>*>(iholder.get());
        return holder->instance_;
    }
    else
    {
        return std::shared_ptr<T>(static_cast<T*>(creatorMap_[typeid(T).name()]()));
    }
}

First we check to see if we have an instance registered and return that instance. We have to do a fairly safe C++ cast here to cast the holder to the right type. If there is no registered instance then we create one using the creator registered for that type and use a slightly more hairy cast to get a pointer to the correct type to return.

Summary

Whils this solution has some flaws, particularly in error checking, hopefully it can be seen how new features of C++ 11 can be used to provide a reasonably elegant solution to the problem of Dependency Injection using an IOC Container.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

jsolutions_uk
Software Developer jSolutions Ltd
United Kingdom United Kingdom
John Cumming has been working in software engineering for over 15 years. With qualifications in a mechanical engineering discipline and initial employment in chemical engineering research, he has engineering experience and knowledge to bring to a variety of software projects.
 
John has a wide range of experience in a variety of technologies, including web applications, distributed architectures and desktop applications and has applied his knowledge and experience in a variety of roles in R&D projects, Integration Consultancy and Enterprise Architecture.
 
John is experienced in Agile development processes such as XP and Scrum and is a Certified Scrum Professional.
 
* C++ - UNIX, Windows, COM, MFC, ATL, CLI, CORBA, Qt, boost
* C# - .NET 4.5, WPF, Prism, XAML
* XML - XML, XSLT, XSD, SOAP, XSL-FO
* Security – Cryptography, Key Management, Smart Cards
* Architecture & Design – EA, Zachman, UML, BPM
* Agile and Scrum
 
LinkedIn
Facebook
jSolutions
Group type: Organisation (No members)


Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140421.2 | Last Updated 1 Apr 2013
Article Copyright 2013 by jsolutions_uk
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid