如何有效地为现代 C++ 中指向虚拟基类的指针向量分配空间
Posted
技术标签:
【中文标题】如何有效地为现代 C++ 中指向虚拟基类的指针向量分配空间【英文标题】:How to efficiently allocate space for vector of pointers to virtual base class in modern C++ 【发布时间】:2015-06-19 08:01:08 【问题描述】:我有以下数据模型
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++ 方式有效地做到这一点
如果所有对象的大小都相同? 如果大小不同?【问题讨论】:
这里看不到推导,怎么管理int create() new DerivedX();
:返回类型与返回值不对应。
代码被简化为无意义:我无法从中得出结论,因为有这么多错误,我如何区分“预期特征”和“错字”?仅通过文本的其余部分,此时为什么要阅读代码? ;)
你需要那个原型数组来做其他事情还是只是用它来填充向量?
代码中大约一半的行无法编译。
如果你使用 C++11,你可以调用带有大小的向量构造函数(在你的情况下是 n * 无论如何),所以它首先分配足够的内存
【参考方案1】:
在我看来,你的很多性能问题可以通过使用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>();
【讨论】:
我会检查unique_ptr
将如何提供帮助,谢谢。然而,大部分时间可以通过分配更大的Base
块来节省,而不是为每个我认为的调用 new。
在这种情况下,您可以查看小型对象分配器,例如来自 Loki 或 Boost.Pool 的分配器。【参考方案2】:
创建一个变体样式类型橡皮擦,使所有内容看起来像Base
:
template<class T>struct tagusing 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++ 喜欢值类型。给它想要的东西。
【讨论】:
以上是关于如何有效地为现代 C++ 中指向虚拟基类的指针向量分配空间的主要内容,如果未能解决你的问题,请参考以下文章