如何在现代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++的方式有效地做到这一点
- 如果所有对象的大小都相同?
- 如果大小不同?
,您的许多性能问题都可以通过使用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
的设计方式是,这已经摊销了恒定的时间复杂度,但是我们可以采取任何措施来缓解这种情况,特别是对于巨大的向量,都将节省时间。
您的新代码可能如下所示:
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>();
创建一个变体样式类型的橡皮擦,使一切看起来像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::unordered_map 项的访问崩溃
- 基类可以声明虚拟方法但不定义它吗?仍然在派生类中定义
- 虚拟基类初始化
- googletest:测试基类具有纯虚拟方法的派生类时的核心转储
- 有没有办法在没有虚拟的情况下使用基类指针调用派生类函数
- 我是否需要在虚拟继承类的构造函数中初始化基类以解决菱形继承问题?
- 虚拟基类在内部如何工作?编译器如何解析对基方法的调用?
- 无法使用在子类中定义的虚拟getter实现基类
- C++虚拟函数:基类函数是调用的,而不是派生的
- 关于C++从派生类调用在基类中实现的虚拟函数的问题
- 派生类调用非公共基类虚拟函数
- 使用基类虚拟方法
- 强制调用基类虚拟函数
- 强制调用长链下的基类虚拟函数
- 通过派生类虚拟方法调用基类虚拟方法