Хранить шаблонные объекты в качестве объектов-членов

предположим, что у вас есть такой код:

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

Объект "Менеджер" создается один раз и вызывается с несколькими различными типами "Т", но каждый тип T вызывается много раз. Это может быть в упрощенной форме, например

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);
}

Теперь профилирование показало, что построение Worker<T> является дорогостоящей операцией, и его следует избегать, чтобы построить его N раз (в пределах doSomething<T>). По соображениям безопасности потока вполне нормально иметь один Worker<int>, один Worker<char> и Worker<float> за "Менеджер", но не один Worker<int> для всех менеджеров. Поэтому обычно я делаю "рабочим" переменную-член. Но как я могу сделать это в коде выше? (Я не знаю заранее, какие "T" будут использоваться).

Я нашел решение, использующее std:: map, но он не является полностью типичным и, конечно, не очень элегантным. Можете ли вы предложить типичный способ, не создавая Worker<T> чаще одного раза за "Т" без виртуальных методов?

(обратите внимание, что Worker не является производным от любого базового класса шаблона-аргумента).

Спасибо за любое решение!

Ответ 1

Вы можете использовать что-то вроде std::map<std::type_info,shared_ptr<void> > следующим образом:

#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;
}

Это типично, потому что мы используем type_info как индекс в карте. Кроме того, рабочие удаляются, даже если они находятся в shared_ptr<void>, потому что удаляемый файл копируется из исходного shared_ptr<Worker<T> > s, и тот вызывает соответствующий конструктор. Он также не использует виртуальные функции, хотя стирание всех типов (и это одно) где-то использует что-то вроде виртуальных функций. Здесь он находится в shared_ptr.

Факторинг независимого от шаблона кода из findWorker в функцию без шаблона для уменьшения раздувания кода остается в качестве упражнения для читателя:)

Спасибо всем комментаторам, которые указали на ошибку при использовании type_info в качестве ключа напрямую.

Ответ 2

Вы можете добавить std::vector из boost::variant или boost::any в качестве члена вашего класса. И приложите к нему любого рабочего, которого вы хотите.

РЕДАКТИРОВАТЬ: Код ниже пояснит, как

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);
  }
};

Ответ 3

Я уже работал над ответом, похожим на mmutz, когда он опубликовал его. Вот полное решение, которое компилируется и запускается в GCC 4.4.3. Он использует RTTI и полиморфизм, чтобы лениво построить Worker<T> и сохранить их на карте.

#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*> не работает, потому что type_info не является конструктивным. Я использовал map<const std::type_info*, BaseWorker*>. Мне просто нужно проверить, что typeid (T) гарантированно всегда возвращает ту же ссылку (я думаю, что это так).


Не имеет значения, вернет ли та же ссылка typeid(T), потому что я всегда использую type_info::before для всех сравнений.

Ответ 4

что-то вроде этого будет работать:

struct Base { };
template<class T> struct D : public Base { Manager<T> *ptr; };
...
struct Manager {
  ...
Base *ptr;
};