我有一个单一类型集合,其类型仅在运行时才知道。一旦定义了类型,它就永远不会改变。我目前正在向量中存储指向对象的指针,如下所示:
std::vector<Animal*> v;
我想知道是否可以将实例存储在连续的内存中。我的目的是编写一个更加缓存友好的代码,并更快地遍历容器。
我可以为每个向量的元素使用boost :: variant,例如,
std::vector<boost::variant< Cat, Dog > >
但如果sizeof(Dog)
比sizeof(Cat)
大得多,那么在对象类型为Cat
的情况下会浪费内存。
我也可以使用Variant的容器:
boost::variant< std::vector<Cat>, std::vector<Dog> >
但我不知道在这种情况下迭代器会是多少,如果它们会引入更多的开销。
指针矢量是否接近“我们能做的最好?”
更多信息:对象的大小在50到250个字节之间,容器长度在10到1M之间,我必须在容器上迭代一百万次。
谢谢。
编辑:我在这里找到了类似的问题(也提出了很好的建议): How to write cache friendly polymorphic code in C++?
答案 0 :(得分:4)
我打算把我的评论变成答案。
我想说最好的办法是将所有的狗放入vector<Dog>
并将所有的猫放入vector<Cat>
,然后分别对它们进行迭代。这样你就可以保持每个载体的最佳包装。
使用某些CRTP,您可以将其自动化,以便轻松添加更多动物。
一些例子:
template <typename T>
class Container{
public:
static std::vector<T> m_elements; //static vector will contain animals
//overloaded operator new adds the Animal to m_elements
void* operator new(size_t){
m_elements.push_back( T{} );
return &m_elements[m_elements.size() - 1];
}
};
template <typename T> std::vector<T> Container<T>::m_elements;
//some example animals
class Dog : public Container<Dog>{
public:
std::string woof;
Dog( char* s = "woof" ){
woof = s;
}
};
class Cat : public Container<Cat>{
public:
std::string meow;
Cat( char* s = "meow" ){
meow = s;
}
};
int main(){
new Dog( "woof" );
new Dog( "rrawoof" );
new Cat( "meow" );
new Cat( "meweeow" );
//easy iteration
for( auto dog : Dog::m_elements )
std::cout << dog.woof << "\n";
for( auto cat : Cat::m_elements )
std::cout << cat.meow << "\n";
std::cout << "end";
}
重载new
是否是一个好主意是一个不同的问题,但它对于show来说很不错。
答案 1 :(得分:2)
对 - 完全重写,更简单。
我同意s3rius你仍然应该使用std :: vector。理想情况下,如果你要存放猫,你会使用......
std::vector<Cat>
如果你要存放狗,你会喜欢......
std::vector<Dog>
但是,您需要运行时多态来选择您正在处理的情况。
策略设计模式的一种方式是(或受其启发)。为这些向量的接口定义基类,并有一个模板类实现包含向量的接口。
class Animals_IF
{
public:
virtual int size () const = 0;
};
template<typename T> class Animals_Vector
{
private:
std::vector<T> store;
public:
int size () const;
};
template<typename T> int Animals_Vector<T>::size () const
{
return store.size ();
}
此处的问题是界面无法提及Cat
或Dog
,因为它不知道具体类型,这当然是我选择size
作为上述示例方法的原因
一种解决方案是使用boost::variant
可能的类型传递值,因此每个策略/包装类可以在使用它们之前检查它获得的值是否是正确的类型。可以通过(非模板)基类中的模板方法处理变量中的包装/展开值。
如果所有包装和解包都变得低效,则必须确定要处理的是哪种情况,然后通过正确的策略/包装类型(而不是基类)进行调用。要做到这一点,请拥有所有策略/包装器案例的boost :: variant。这并不妨碍你也有一个指向基类的指针。实际上,将指针指向基类和boost::variant
包装在一个类中(在需要时使用模板方法)。
class Animals_IF
{
public:
typedef boost::variant<Cat,Dog> Animal;
virtual int size () const = 0;
template<typename T> void slow_push (const T &p)
{
push_ (Animal (p));
}
private:
virtual void slow_push_ (const Animal &p) = 0;
};
template<typename T> class Animals_Vector
{
public:
int size () const;
void fast_push (const T &p);
private:
std::vector<T> store;
void slow_push_ (const Animal &p);
};
template<typename T> int Animals_Vector<T>::size () const
{
return store.size ();
}
template<typename T> void Animals_Vector<T>::fast_push (const T &p)
{
store.push (p);
}
template<typename T> void Animals_Vector<T>::slow_push_ (const Animal &p)
{
const T* item = boost::get<T> (&p);
if (T) store.push (*item);
// else throw?
}
class Animals
{
public:
int size () const
{
// null check needed?
return ptr->size ();
}
template<typename T> void slow_push (const T &p)
{
// null check needed?
ptr->slow_push (p);
}
template<typename T> void fast_push (const T &p)
{
Animals_Vector<T> *lptr = boost::get<T> (&store);
if (lptr) lptr->fast_push (p);
// else throw?
}
private:
Animals_IF* ptr;
boost::variant<Animals_Vector<Cat>,Animals_Vector<Dog>> store;
};
如果共享接口无法真正提供任何内容(因为每个方法都需要传递值,并且包装/解包作为变体是不可接受的),整个策略是不必要的。只需要一个不同的std :: vector类型的boost ::变体。
此外,上面的fast_push
不会很快,因为push
太简单而无法受益 - 这个想法是对于可以避免重复运行时类型检查的复杂方法,该方法更快在前面完成一次。
BTW - 很好的问题。