如何设计符合std :: any标准实现的存储? [英] How can I design storage that conforms to the standard's implementation of std::any?
问题描述
标准工作草案(n4582,20.6.3,p.552)对std::any
的实现提出以下建议:
The standard working draft (n4582, 20.6.3, p.552) states the following suggestion for implementations of std::any
:
实现应避免为包含的小对象使用动态分配的内存. [示例:构造的对象仅包含一个int. [-结束示例]这种小对象优化只能应用于is_nothrow_move_constructible_v为true的类型T.
Implementations should avoid the use of dynamically allocated memory for a small contained object. [ Example: where the object constructed is holding only an int. —end example ] Such small-object optimization shall only be applied to types T for which is_nothrow_move_constructible_v is true.
据我所知,std::any
可以通过类型擦除/虚拟函数和动态分配的内存轻松实现.
As far as I know, std::any
can be easily implemented through type erasure/virtual functions and dynamically allocated memory.
如果std::any
在销毁时不知道编译时间信息,又如何避免动态分配并仍然销毁这些值;如何设计符合标准建议的解决方案?
How can std::any
avoid dynamic allocation and still destroy such values if no compile time information is known at the time of destruction; how would a solution that follows the standard's suggestion be designed?
如果有人希望看到非动态部分的可能实现,我已经在代码审查上发布了一个:
If anyone wants to see a possible implementation of the non-dynamic part, I've posted one on Code Review: https://codereview.stackexchange.com/questions/128011/an-implementation-of-a-static-any-type
这里的答案太长了.它基于Kerrek SB关于以下评论的建议.
It's a little too long for an answer here. It's based on the suggestions of Kerrek SB on the comments below.
推荐答案
通常,any
接受任何内容并从中动态分配一个新对象:
Typically, any
takes anything and dynamically allocates a new object from it:
struct any {
placeholder* place;
template <class T>
any(T const& value) {
place = new holder<T>(value);
}
~any() {
delete place;
}
};
我们使用placeholder
是多态的这一事实来处理我们的所有操作-销毁,强制转换等.但是现在我们要避免分配,这意味着我们要避免多态给我们带来的所有好处-并且需要重新实现它们.首先,我们要有一些联合:
We use the fact that placeholder
is polymorphic to handle all of our operations - destruction, cast, etc. But now we want to avoid allocation, which means we avoid all the nice things that polymorphism gives us - and need to reimplement them. To start with, we'll have some union:
union Storage {
placeholder* ptr;
std::aligned_storage_t<sizeof(ptr), sizeof(ptr)> buffer;
};
我们有一些template <class T> is_small_object { ... }
来决定我们要执行ptr = new holder<T>(value)
还是new (&buffer) T(value)
.但是构造并不是我们唯一要做的事情-我们还必须进行销毁和类型信息检索,这取决于我们所处的情况而有所不同.我们正在执行delete ptr
或正在执行static_cast<T*>(&buffer)->~T();
,后者取决于跟踪T
!
where we have some template <class T> is_small_object { ... }
to decide whether or not we're doing ptr = new holder<T>(value)
or new (&buffer) T(value)
. But construction isn't the only thing we have to do - we also have to do destruction and type info retrieval, which look different depending on which case we're in. Either we're doing delete ptr
or we're doing static_cast<T*>(&buffer)->~T();
, the latter of which depends on keeping track of T
!
因此,我们介绍了自己的类似于vtable的东西.然后,我们的any
将保留:
So we introduce our own vtable-like thing. Our any
will then hold onto:
enum Op { OP_DESTROY, OP_TYPE_INFO };
void (*vtable)(Op, Storage&, const std::type_info* );
Storage storage;
您可以为每个操作创建一个新的函数指针,但是这里可能缺少其他几个操作(例如OP_CLONE
,这可能需要将传入的参数更改为union
...),而您不想只是用一堆函数指针来夸大您的any
大小.这样,我们就损失了一点点性能,以换取尺寸上的巨大差异.
You could instead create a new function pointer for each op, but there are probably several other ops that I'm missing here (e.g. OP_CLONE
, which might call for changing the passed-in argument to be a union
...) and you don't want to just bloat your any
size with a bunch of function pointers. This way we lose a tiny bit of performance in exchange for a big difference in size.
在构造时,我们同时填充storage
和vtable
:
On construction, we then populate both the storage
and the vtable
:
template <class T,
class dT = std::decay_t<T>,
class V = VTable<dT>,
class = std::enable_if_t<!std::is_same<dT, any>::value>>
any(T&& value)
: vtable(V::vtable)
, storage(V::create(std::forward<T>(value))
{ }
其中我们的VTable
类型类似于:
where our VTable
types are something like:
template <class T>
struct PolymorphicVTable {
template <class U>
static Storage create(U&& value) {
Storage s;
s.ptr = new holder<T>(std::forward<U>(value));
return s;
}
static void vtable(Op op, Storage& storage, const std::type_info* ti) {
placeholder* p = storage.ptr;
switch (op) {
case OP_TYPE_INFO:
ti = &typeid(T);
break;
case OP_DESTROY:
delete p;
break;
}
}
};
template <class T>
struct InternalVTable {
template <class U>
static Storage create(U&& value) {
Storage s;
new (&s.buffer) T(std::forward<U>(value));
return s;
}
static void vtable(Op op, Storage& storage, const std::type_info* ti) {
auto p = static_cast<T*>(&storage.buffer);
switch (op) {
case OP_TYPE_INFO:
ti = &typeid(T);
break;
case OP_DESTROY:
p->~T();
break;
}
}
};
template <class T>
using VTable = std::conditional_t<sizeof(T) <= 8 && std::is_nothrow_move_constructible<T>::value,
InternalVTable<T>,
PolymorphicVTable<T>>;
,然后我们仅使用该vtable来实现我们的各种操作.喜欢:
and then we just use that vtable to implement our various operations. Like:
~any() {
vtable(OP_DESTROY, storage, nullptr);
}
这篇关于如何设计符合std :: any标准实现的存储?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!