繁体   English   中英

如何在现代C ++中有效地为虚拟基类的指针向量分配空间

[英]How to efficiently allocate space for vector of pointers to virtual base class in modern C++

我有以下数据模型

struct Base {
    int x_;
    int y_;
    int z_;

    virtual int getId() const;
    virtual int getValue() const = 0;
    virtual Base* create() const = 0;
    bool operator<(const Base &other);
};

struct Derived0 : public Base {
    virtual Derived0* create() const { return new Derived0(); };
    virtual int getId() const;
    virtual int getValue() const;
};
//...
struct DerivedN : public Base {
    virtual DerivedN* create() const { return new DerivedN(); };
    virtual int getId() const;
    virtual int getValue() const;
};

并按照以下方式填写(简体)

int n = 0;
std::shared_ptr<Base> templ[100];

templ[n++] = std::make_shared<Derived0>();
//...
templ[n++] = std::make_shared<DerivedN>();

std::vector<std::shared_ptr<Base>> b;

for (int i = 0; i < n; i++) {
    while (...) { // hundreds of thousands iterations
        std::shared_ptr<Base> ptr(templ[i]->create());
        // previous call consumes most of the time
        //...
        b.push_back(ptr);
    }
}

std::sort(b.begin(), b.end());
// ...

由于我需要大量的派生对象,所以我想知道初始化是否可以更有效地完成。 在所示的情况下,大多数时间都花在创建单个共享指针上。

我尝试了一种预分配Base对象数组的方法(因为所有“ Derived具有相同的大小),为每个模板强制转换虚拟类型并存储指向该数组的原始指针。 毫不奇怪,这种方法快很多倍。 但是,这并不干净,无法使用vector并且内存管理存在问题。

有人可以给我建议吗,如何以C ++的方式有效地做到这一点

  • 如果所有对象的大小都相同?
  • 大小是否不同?

创建一个变体样式类型的橡皮擦,使一切看上去都像Base

template<class T>struct tag{using type=T;};

template<class Base, class...Derived>
struct poly {
  Base* get(){
    return const_cast<Base*>( const_cast<poly const*>( this )->get() );
  }
  Base const* get()const{
    if (!ops) return nullptr;
    return ops->to_base(&raw);
  }
  Base* operator->(){ return get(); }
  Base const* operator->()const{ return get(); }
  Base& operator*(){ return *get(); }
  Base const& operator*()const{ return *get(); }
  explicit operator bool()const{ return get(); }

  template<class T,class...Args,
    class=std::enable_if<
    /* T is one of Derived... */
    >
  >
  void emplace(tag<T>,Args&&...args){
    cleanup();
    ops=&ops_for<T>();
    new(&raw)T(std::forward<Args>(args)...);
  }        
  poly& operator=(poly const& o){
    if (this==&o)return *this;
    cleanup();
    if (!o->ops) return *this;
    o->ops.copy_ctor( &raw, &o.raw );
    ops=o->ops;
    return *this;
  }
  poly& operator=(poly&&o){
    if (this==&o)return *this;
    cleanup();
    if (!o->ops) return *this;
    o->ops.move_ctor( &raw, &o.raw );
    ops=o->ops;
    return *this;
  }

  poly(poly const& o){
    if (!o->ops)return;
    o->ops.copy_ctor(&raw,&o.raw);
    ops=o->ops;
  }
  poly(poly&& o){
    if (!o->ops)return;
    o->ops.move_ctor(&raw,&o.raw);
    ops=o->ops;
  }

private:
  void cleanup(){
    if (ops) ops->dtor(&raw);
    ops=nullptr;
  }
  struct erase_ops{
    void(*copy_ctor)(void*lhs,void const*rhs);
    void(*move_ctor)(void*lhs,void*rhs);
    void(*dtor)(void*ptr);
    Base const*(*to_base)(void const*ptr);
  };
  template<class D>
  static erase_ops const& ops_for(){
    static erase_ops r={
      // ...
    };
    return r;
  };
  erase_ops const* ops=nullptr; // = &ops_for<Derived1>(); etc
  std::aligned_storage< /* size and alignment info */ > raw;
};

实施遗漏,正在打电话。

完成上述操作后,您可以创建poly<Base, Derived1, Derived2, ....的向量。 成本是每个实例增加一个指针。

现在,我们已经复制了大多数虚拟调度,因此我们可以在类型中包括对DerivedN的其余操作(作为虚拟方法实现)并减少其他指针的开销。 如果Base稍大一些,我就不会打扰。

C ++喜欢值类型。 给它想要的东西。

在我看来,您可以通过使用std::unique_ptr并预先保留一些std::vector内存来解决许多性能问题。

std::shared_ptr<Base> ptr(templ[i]->create());

上一行涉及为派生类型和std::shared_ptr控制块动态分配内存。 如果您没有共享所有权语义,那么使用std::unique_ptr可以消除对这些分配之一的需求。

b.push_back(ptr);

当您执行以上足够的时间时,向量将耗尽它为您分配的内存,并尝试分配更多的空间。 std::vector的设计方式可以分摊恒定的时间复杂度,但是我们可以采取任何措施来减轻这种情况,尤其是使用巨大的vector时,可以节省时间。

您的新代码可能类似于:

std::vector<std::unique_ptr<Base>> b;
b.reserve(n * /*number of iterations*/);

for (int i = 0; i < n; i++) {
    while (...) { // hundreds of thousands iterations
        std::unique_ptr<Base> ptr(templ[i]->create());
        //...
        b.push_back(ptr);
    }
}

顺便说一句,您可以通过执行以下操作来限制原型数组创建中的代码重复:

template <class Base, class... Derived, std::size_t... Idx>
auto arrayOfUniqueDerived (std::index_sequence<Idx...>)
{
    std::array<std::unique_ptr<Base>, sizeof...(Derived)> arr;
    (void) std::initializer_list<int> { (arr[Idx] = std::make_unique<Derived>(), 0)... };
    return arr;
}

template <class Base, class... Derived>
auto arrayOfUniqueDerived ()
{
    return arrayOfUniqueDerived<Base,Derived...>(std::index_sequence_for<Derived...>{});
}

然后像这样使用它:

std::array<std::unique_ptr<Base>,3> templ =
      arrayOfUniqueDerived<Base,Derived0,Derived1,Derived2>();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM