Хранить шаблонные объекты в качестве объектов-членов
предположим, что у вас есть такой код:
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;
};