如何设计符合std :: any标准实现的存储? [英] How can I design storage that conforms to the standard's implementation of std::any?

查看:157
本文介绍了如何设计符合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.

在构造时,我们同时填充storagevtable:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆