将模板化对象存储为成员对象

时间:2011-04-28 18:23:36

标签: c++ performance

假设你有这样的代码:

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

“Manager”对象创建一次并使用一些不同类型“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>内)。出于线程安全的原因,每个“Manager”可以有一个Worker<int>,一个Worker<char>Worker<float>,但是对于所有Managers而言不是一个Worker<int>。所以通常我会把“worker”变成一个成员变量。但是我怎么能在上面的代码中这样做呢? (我事先不知道会使用哪个“T”。)

我找到了一个使用std :: map的解决方案,但它不是完全类型安全的,当然也不是很优雅。如果没有虚拟方法,你可以建议一种类型安全的方法而不是每个“T”构建Worker<T>一次吗?

(请注意,Worker不是从任何模板参数的免费基类派生的。)

感谢任何解决方案!

4 个答案:

答案 0 :(得分:7)

您可以使用类似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> >复制的,并且那个人调用了正确的构造函数。它也不使用虚函数,尽管所有类型擦除(并且这是一个)在某处使用类似虚函数的东西。在这里,它位于shared_ptr

将来自findWorker的与模板无关的代码分解为非模板函数以减少代码膨胀是留给读者的练习:)

感谢所有评论员指出直接使用type_info作为密钥的错误。

答案 1 :(得分:2)

您可以添加std::vectorboost::variantboost::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);
  }
};

答案 2 :(得分:2)

当他发布他的时候,我已经在做一个类似于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来进行所有比较。

答案 3 :(得分:0)

这样的事情会起作用:

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