views:

633

answers:

4

I wonder what is the C++ way of using dependency injection? Is that using templates or polymorphic classes? Consider the following code,

class AbstractReader
{
public:
  virtual void Read() = 0;
};

class XMLReader : public AbstractReader
{
public:
  void Read()  { std::cout << "Reading with a XML reader" << std::endl; }
};

class TextFileReader : public AbstractReader
{
public:
  void Read()  { std::cout << "Reading with a Text file reader" << std::endl; }
};

class Parser
{
public:
  Parser(AbstractReader* p_reader) : reader(p_reader) { }
  void StartParsing() { reader->Read();
    // other parsing logic
  }
private:
  AbstractReader* reader;
};

template<class T>
class GenericParser
{
public:
  GenericParser(T* p_reader) : reader(p_reader) { }

  void StartParsing()
  {
    reader->Read();
  }
private:
  T* reader;
};

1 - Which is the best method? GenericParser or Parser? I know if it is GenericParser, inheritance can be removed.

2 - If templates is the way to go, is it OK to write all the code in header files? I have seen many classes using templates writes all the code in header files rather than .h/.cpp combination. Is there any problems in doing so, something like inlining etc?

Any thoughts?

A: 

As to 1. "Best" is relative. Both methods have their pluses and minuses. Templates offer raw speed, but more code is inevitably inlined (yielding more coupling), and the error messages are hard to read. Inheritance is slower and makes objects larger, but it doesn't require inlining (less coupled). It also has relatively better error messages.

For a small library, coupling matters less, and templates can be a good choice. However, as complexity of your library increases, you need to move towards a less coupled approach. If you are unsure of how large your library will grow, or don't need the speed templating would provide (or don't want to deal with the error messages), go with inheritance.

My answer to 2 follows up on 1. Inlining is needed for some consumer templates, therefor requiring code placed in the header. It's a question of coupling. Inlining increases coupling between components and can drastically increase compile times; avoid it unless you want the speed and are sure your library will remain small.

Todd Gardner
+2  A: 

I personally prefer to use the template solution if I know the type of the reader at the compile time itself as I feel there is no run time decision to be taken here hence the polymorphism will be of no use. As far as writing the templates in header files is concerned, you have to do that to avoid getting the linker error. This is because if you write the template method in a cpp, the compiler will not be able to instantiate the template class and hence the linker will give error. Although, a couple of workarounds exist, most of the template code is written in header files.

Naveen
+1 on the important point of *runtime decision*. If you need it then inheritance is the only way.
David Rodríguez - dribeas
A: 

GenericParser or Parser?

Depends on the rest of the code, the problem with the generic parser is that class you are going to inject also has to be a template.
But there is a 3rd more generic way ... boost::function and boost::lambda. All you have to ask for is a function with the correct (from the view of the user of the class) return type and parameters. boost::function< void ()> reader = bind( &TextFile::read, reader ); Now the user class is independent of the reader class and doesn't have to be a template.

class User
{
const boost::function< void ()>& reader;

public:
    void setReader( const boost::function< void ()>& reader ) 
    : reader(reader) {
    }
};


Writes all the code in header files rather than .h/.cpp combination.

That is called the seperation model is there is only one compiler that supports it (Comeau compiler). Start reading "Export" Restriction part 1 and "Export" Restrictions part 2


@CiscoIPPhone Comment on: the problem with the generic parser is that class you are going to inject also has to be a template.

template<class T>
class GenericParser
{
public:
    GenericParser(T* p_reader) : reader(p_reader) { }

    void StartParsing()
    {
        reader->Read();
    }
private:
    T* reader;
};

// Now you have a GeniricParser Interface but your Parser is only usable for 
// TextFileReader 
class Parser
{
public:
    Parser( GenericParser<TextFileReader> p_reader) : reader(p_reader) { }
    void StartParsing() { 
        reader->Read();
    }
private:
    GenericParser<RealParser> reader;
};


//Solution is to make Parser also a template class
template<class T>
class Parser
{
public:
    Parser( GenericParser<T> p_reader) : reader(p_reader) { }
    void StartParsing() { 
        reader->Read();
    }
private:
    GenericParser<T> reader;
};
TimW
It's perfectly possible to use polymorphic function wrappers (nowadays properly called std::tr1::function, soon to be std::function) from many other compilers. Whether the compiler implements export does not affect this.
Daniel Earwicker
Sorry but the seperation model is the answer to his second question ...
TimW
I don't see why you think the class that is going to be injected needs to be a template (the p_reader passed into GenericParser in his example).
CiscoIPPhone
What are you going to pass to Parser? How does Parser knows "WhatNeedsToBeParsed"? Parser(GenericParser< WhatNeedsToBeParsed >
TimW
Thank you for trying to explain, I still don't understand what you are saying. In OPs example Parser and GenericParser are two versions of a class with the same responsibility, they don't interact but they do in your example.Also In your User class will not compile because you are assigning to a const variable, and because you do not assign to it in a constructor.
CiscoIPPhone
Yep, it need to put it in the initializer list. I probably misunderstood the OP ...
TimW
+7  A: 

You don't have a free choice in this, based on how you want to structure your code or header files. The answer is dictated to you by the requirements of your application.

It depends on whether the coupling can be be decided at compile time or must be delayed until runtime.

If the coupling between a component and its dependencies is decided permanently at compile time, you can use templates. The compiler will then be able to perform inlining.

If however the coupling needs to be decided at runtime (e.g. the user chooses which other component will supply the dependency, perhaps through a configuration file) then you can't use templates for that, and you must use a runtime polymorphic mechanism. If so, your choices include virtual functions, function pointers or std::tr1::function.

Daniel Earwicker