类型擦除上下文中的 C++ 内存分配(使用分配器)

C++ Memory allocation (using allocator) in a type erased context

提问人:barsdeveloper 提问时间:2/15/2019 更新时间:2/15/2019 访问量:301

问:

标准 c++ 库中有许多类可能会分配内存,但不接受分配器。其中一些这样做是因为无法在类型擦除的上下文中分配内存。

一个例子是 std::any,它有一个构造函数,该构造函数在其设计的某个时刻接受 Allocator 参数,但由于似乎无法实现而被丢弃。我确实考虑了一段时间,我想知道阻止其实施的确切问题是什么标准的哪些要求不能满足

假设我们从 的基本实现开始。分配内存是微不足道的:any

struct any {

    struct type_interface;

    template <typename T>
    struct type_impl;

    type_interface* value;

    any(T&& value, const Allocator& allocator = Allocator()) {
        using actual_allocator_t
            = std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
        actual_allocator_t actual_allocator;
        // do allocate
        // do construct
        // assign obtained pointer
    }
};

问题显然在于我们丢失了最初分配对象的分配器。一种技巧是创建一个方法,声明一个静态变量来存储该分配器。type_impl<T>

template <typename Allocator>
auto& get_allocator(const Allocator& allocator) {
    using actual_allocator_t = std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
    // static variable: initialized just on the first call
    static actual_allocator_t actual_allocator(allocator);
    return actual_allocator;
}
// and the constructor is now
any::any(T&& value, const Allocator& allocator = Allocator()) {
    auto actual_allocator = get_allocator(allocator);
    // do allocate
    // do construct
    // assign obtained pointer
}

现在我们可以检索相同的对象来解除分配之前分配的对象。最后一个要解决的问题是释放。对象无法解除分配自身,因此可以使用相同的技巧来包装解除分配逻辑并通过接口使用它。

// Deallocator interface
struct deallocate_interface{
    virtual void deallocate(void*) {};
};

template <typename Allocator>
struct deallocate_wrapper{
    virtual void deallocate(void* ptr) {
        std::allocator_traits<Allocator>::deallocate(
            this->allocator,
            reinterpret_cast<typename Allocator::value_type*>(ptr),
            1u
        );
    }
};

并且,以同样的方式将其存储到静态方法中:

template <typename Allocator>
deallocate_interface& get_deallocator(Allocator& allocator) {
    auto& actual_allocator = get_allocator(allocator);
    // static variable: initialized just on the first call
    static deallocate_wrapper<std::decay_t<decltype(actual_allocator)>> deallocator(actual_allocator);
    return deallocator;
}

我看到的唯一限制是,此实现对相同类型的所有对象使用相同的分配器,这意味着在复制/移动的情况下,分配器不会被复制/移动。但这难道不比没有分配器好吗? 我在这里测试了代码(https://github.com/barsan-md/type-erasure-and-allocation),看看它是否按预期工作。 可能的输出为:

Begin
Allocator: 0x55ec6563a132, allocating:   0x55ec667e4280
Constructed: 0x55ec667e4280, print: Hello
Allocator: 0x55ec6563a132, allocating:   0x55ec667e42b0
Constructed: 0x55ec667e42b0, print: World
Allocator: 0x55ec6563a140, allocating:   0x55ec667e42e0
Constructed: 0x55ec667e42e0, print: 12345
Destroyed:   0x55ec667e42e0, print: 12345
Allocator: 0x55ec6563a140, deallocating: 0x55ec667e42e0
Destroyed:   0x55ec667e42b0, print: World
Allocator: 0x55ec6563a132, deallocating: 0x55ec667e42b0
Destroyed:   0x55ec667e4280, print: Hello
Allocator: 0x55ec6563a132, deallocating: 0x55ec667e4280
End
C++ 内存管理 类型擦除 C++-标准库 分配器

评论

1赞 NathanOliver 2/15/2019
设计的一个问题是无法更改不同实例的分配器。如果您设置了分配器,那么这就是所有 将使用的分配器。anyany::type_impl<std::string>any::type_impl<std::string>
0赞 barsdeveloper 2/15/2019
但分配器只提供内存的分配和释放。使用相同的分配器实例进行多个分配有什么问题。从分配器中,pov 必须像 std::list 一样分配多个节点。
0赞 NathanOliver 2/15/2019
假设我有一个想要使用堆栈分配器的任意一个,然后我想拥有另一个为同一类型进行动态分配的分配器。在这种情况下,我不能这样做。
0赞 barsdeveloper 2/15/2019
在这种情况下,分配器将不同,并且 .对于同一对分配器和 T,它们是相同的stack_alloc<T>dynamic_alloc<T>
0赞 barsdeveloper 2/15/2019
对不起,我看到了混乱的可能来源。在示例中,我展示了get_allocator作为一个独立的函数,而实际上它是 any::type_impl 类的成员,这就是它获取有关要分配的实际类型的信息的方式。github.com/barsan-md/type-erasure-and-allocation/blob/......

答: 暂无答案