10

suppose you have some code like this:

struct Manager
{
  template <class T> 
  void doSomething(T const& t)
  {
    Worker<T> worker;
    worker.work(t);
  }
};

A "Manager" object is created once and called with a few diffent types "T", but each type T is called many times. This might be, in a simplified form, like

Manager manager;
const int N = 1000;
for (int i=0;i<N;i++)
{
  manager.doSomething<int>(3);
  manager.doSomething<char>('x');
  manager.doSomething<float>(3.14);
}

Now profiling revealed that constructing a Worker<T> is a time-costly operation and it should be avoided to construct it N times (within doSomething<T>). For thread-safety reasons it is ok to have one Worker<int>, one Worker<char> and Worker<float> per "Manager", but not one Worker<int> for all Managers. So usually I would make "worker" a member variable. But how could I do this in the code above? (I do not know in advance which "T"s will be used).

I have found a solution using a std::map, but it is not fully typesafe and certainly not very elegant. Can you suggest a typesafe way without constructing Worker<T> more often than once per "T" without virtual methods?

(please note that Worker is not derived from any template-argument free base class).

Thanks for any solution!

Philipp
  • 11,549
  • 8
  • 66
  • 126
  • Sounds like a job for a tuple, though I'm not familiar with the syntax, so I can't provide an example. – Benjamin Lindley Apr 28 '11 at 18:30
  • 1
    What about making your worker class a singleton to have only one instance per class? (since Worker, Worker, ..., or different classes, it is possible) - Worker::instance_get().work(t) – Julio Guerra Apr 28 '11 at 18:55
  • If you are happy to use `boost::mpl`, check out my own question and answer, you may be able to adapt it to your needs. http://stackoverflow.com/questions/4798169/is-there-a-way-to-break-out-of-boostmpl-for-each – Nim Apr 28 '11 at 19:27

4 Answers4

7

You can use something like a std::map<std::type_info,shared_ptr<void> > like this:

#include <map>
#include <typeinfo>
#include <utility>
#include <functional>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

// exposition only:
template <typename T>
struct Worker {
    void work( const T & ) {}
};

// wrapper around type_info (could use reference_wrapper,
// but the code would be similar) to make it usable as a map<> key:
struct TypeInfo {
    const type_info & ti;
    /*implicit*/ TypeInfo( const type_info & ti ) : ti( ti ) {}
};

// make it LessComparable (could spcialise std::less, too):
bool operator<( const TypeInfo & lhs, const TypeInfo & rhs ) {
    return lhs.ti.before( rhs.ti );
}

struct Manager
{
    map<TypeInfo,shared_ptr<void> > m_workers;
    template <class T> 
    Worker<T> * findWorker()
    {
        const map<TypeInfo,shared_ptr<void> >::const_iterator
        it = m_workers.find( typeid(T) );
        if ( it == m_workers.end() ) {
            const shared_ptr< Worker<T> > nworker( new Worker<T> );
            m_workers[typeid(T)] = nworker;
            return nworker.get();
        } else {
            return static_cast<Worker<T>*>( it->second.get() );
        }
    }
    template <typename T>
    void doSomething( const T & t ) {
        findWorker<T>()->work( t );
    }
};

int main() {

    Manager m;
    m.doSomething( 1 );
    m.doSomething( 1. );

    return 0;
}

This is typesafe because we use type_info as an index into the map. Also, the workers are properly deleted even though they're in shared_ptr<void>s because the deleter is copied from the original shared_ptr<Worker<T> >s, and that one calls the proper constructor. It also doesn't use virtual functions, although all type erasure (and this is one) uses something like virtual functions somewhere. Here, it's in shared_ptr.

Factoring the template-independent code from findWorker into a non-template function to reduce code bloat is left as an exercise for the reader :)

Thanks to all commenters who pointed out the mistake of using type_info as the key directly.

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
  • This does seem to meet all the OP's requirements. – Mark B Apr 28 '11 at 18:53
  • 3
    @mmutz: type_info is not assignable. Did you try compiling this? – Emile Cormier Apr 28 '11 at 19:13
  • 1
    @mmutz: `type_info` is also not required to provide `operator<`. – Emile Cormier Apr 28 '11 at 19:28
  • @Emile: `type_info` has the `before` method, so you can introduce a comparision functor if it doesn't provide `operator<`. – Xeo Apr 28 '11 at 19:40
  • @Emile: I was under the impression that `less` was required to be properly specialised by the standard, but I guess I was wrong. And while I don't need assignment of `type_info` I do need it to be copy-constructable, which it also isn't. A small wrapper type fixes this. Fully compilable sample code in edit. Thanks for your comments! – Marc Mutz - mmutz Apr 28 '11 at 19:47
  • @mmutz: You also can't safely delete your `Worker`s because their type end up being erased when stored in `shared_ptr`. – Emile Cormier Apr 28 '11 at 19:54
  • 2
    @Emile: while their type is erased, they still have the correct deleter. – Marc Mutz - mmutz Apr 28 '11 at 19:56
  • @mmutz: +1 I didn't know shared_ptr copied the deleter. Good to know! With `shared_ptr`, you avoid virtual function call overhead for `Worker::work`. Nice. – Emile Cormier Apr 28 '11 at 20:01
  • @mmutz: thanks a lot! I had a similar solution with void* instead of shared_ptr and some other mechanisms to call the correct deleter. Now the code looks *much* better! – Philipp Apr 29 '11 at 06:05
2

You can add std::vector of boost::variants or boost::anys as member of your class. And append to it any worker you want.

EDIT: The code bellow will explain how

struct Manager
{
  std::vector<std::pair<std::type_info, boost::any> > workers;
  template <class T> 
  void doSomething(T const& t)
  {
    int i = 0;
    for(; i < workers.size(); ++i)
        if(workers[i].first == typeid(T))
            break;
    if(i == workers.size())
        workers.push_back(std::pair<std::type_info, boost::any>(typeid(T).name(), Worker<T>());
    any_cast<T>(workers[i]).work(t);
  }
};
Mihran Hovsepyan
  • 10,810
  • 14
  • 61
  • 111
2

I was already working on an answer similar to mmutz's by time he posted his. Here's a complete solution that compiles and runs under GCC 4.4.3. It uses RTTI and polymorphism to lazily construct Worker<T>s and store them in a map.

#include <iostream>
#include <typeinfo>
#include <map>

struct BaseWorker
{
    virtual ~BaseWorker() {}
    virtual void work(const void* x) = 0;
};

template <class T>
struct Worker : public BaseWorker
{
    Worker()
    {
        /* Heavyweight constructor*/
        std::cout << typeid(T).name() << " constructor\n";
    }

    void work(const void* x) {doWork(*static_cast<const T*>(x));}

    void doWork(const T& x)
        {std::cout << typeid(T).name() << "::doWork(" << x << ")\n";}
};

struct TypeofLessThan
{
    bool operator()(const std::type_info* lhs, const std::type_info* rhs) const
        {return lhs->before(*rhs);}
};

struct Manager
{
    typedef std::map<const std::type_info*, BaseWorker*, TypeofLessThan> WorkerMap;

    ~Manager()
    {
        // Delete all BaseWorkers in workerMap_
        WorkerMap::iterator it;
        for (it = workerMap_.begin(); it != workerMap_.end(); ++it)
            delete it->second;
    }

    template <class T>
    void doSomething(T const& x)
    {
        WorkerMap::iterator it = workerMap_.find(&typeid(T));
        if (it == workerMap_.end())
        {
            it = workerMap_.insert(
                std::make_pair(&typeid(T), new Worker<T>) ).first;
        }
        Worker<T>* worker = static_cast<Worker<T>*>(it->second);
        worker->work(&x);
    }

    WorkerMap workerMap_;
};

int main()
{
    Manager manager;
    const int N = 10;
    for (int i=0;i<N;i++)
    {
      manager.doSomething<int>(3);
      manager.doSomething<char>('x');
      manager.doSomething<float>(3.14);
    }
}

map<std::type_info, BaseWorker*> doesn't work because type_info is not copy-constructible. I had do use map<const std::type_info*, BaseWorker*>. I just need to check that typeid(T) is guaranteed to always return the same reference (I think it is).


It doesn't matter whether or not typeid(T) returns the same reference, because I always use type_info::before do to all comparisons.

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • Is it safe to take the address of `type_info`? http://msdn.microsoft.com/en-us/library/70ky2y6k(v=vs.80).aspx says "construct a (temporary) object" and I really don't know how to read that statement. – pmr Apr 28 '11 at 19:48
  • @Emile: you're not passing `t` to `work()` in `doSomething()`. – Marc Mutz - mmutz Apr 28 '11 at 20:10
  • I don't believe this works, because the virtual base method work() is never implemented this way, right? – Philipp Apr 29 '11 at 06:07
  • @Philipp: You're right, it doesn't work. I "fixed" it without bothering to recompile it. I don't know if I can even make it work at all. I might have to scrap this answer. – Emile Cormier Apr 29 '11 at 15:17
  • Fixed my code and actually tested it this time. I used type erasure to get around the problem of not being able to declare a virtual template function. – Emile Cormier Apr 29 '11 at 15:24
  • Made another tweak to avoid virtual function call overhead for `Worker::work`. – Emile Cormier Apr 29 '11 at 15:36
0

something like this will work:

struct Base { };
template<class T> struct D : public Base { Manager<T> *ptr; };
...
struct Manager {
  ...
Base *ptr;
};
tp1
  • 288
  • 1
  • 3