Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Store templated objects as member objects

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!

like image 905
Philipp Avatar asked Apr 28 '11 18:04

Philipp


3 Answers

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.

like image 132
Marc Mutz - mmutz Avatar answered Oct 24 '22 02:10

Marc Mutz - mmutz


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);
  }
};
like image 24
Mihran Hovsepyan Avatar answered Oct 24 '22 02:10

Mihran Hovsepyan


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.

like image 30
Emile Cormier Avatar answered Oct 24 '22 00:10

Emile Cormier