类型仅在运行时已知的同类容器

Homogeneous container where type is known only at runtime

本文关键字:同类 运行时 类型      更新时间:2023-10-16

我有一个单类型集合,其类型仅在运行时已知。定义类型后,它将永远不会更改。我目前正在向量中存储指向对象的指针,如下所示:

std::vector<Animal*> v;

我想知道是否可以将实例存储在连续内存中。我的目的是编写一个对缓存更友好的代码,并更快地遍历容器。

我可以为每个向量的元素使用 boost::variant ,例如,

std::vector<boost::variant< Cat, Dog > >

但是,如果sizeof(Dog)sizeof(Cat)大得多,那么在对象类型为 Cat 的情况下,内存就会浪费

我还可以使用容器的变体:

boost::variant< std::vector<Cat>, std::vector<Dog> >

但我不知道在这种情况下迭代器会如何,以及它们是否会引入更多的开销。

"指针向量方法"是我们能做的最好的事情吗?

更多信息:对象的大小在 50 到 250 字节之间,容器长度在 10K 到 1M 元素之间,我必须遍历容器一百万次。

谢谢。

编辑:我在这里发现了一个类似的问题(也有很好的建议(:如何在C++中编写缓存友好的多态代码?

我要把我的评论变成一个答案。

我想说你最好的选择是把所有的狗放进一个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是否是一个好主意是一个不同的问题,但它对展示来说很好。

对 - 在这里完全重写,而且更简单。

我同意 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 ();
}

这里的问题是界面不能提及CatDog,因为它不知道具体类型,这当然是我选择size作为上面示例方法的原因。

一种解决方案是使用可能类型的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 类型进行提升::变体。

此外,上面的fast_push不会很快,因为push太简单而无法受益 - 这个想法是,该方法对于复杂的方法来说更快,可以通过预先完成一次来避免重复的运行时类型检查。

顺便说一句 - 好问题。