为什么我不能在std :: vector <T>中包装T*?

ein*_*ica 4 c++ vector wrapper c++11

我有一个T*缓冲区,其中len包含类型的元素T.std::vector<T>出于某些原因,我需要以a的形式提供这些数据.据我所知,我无法构造一个使用我的缓冲区作为其内部存储的向量.这是为什么?

笔记:

  • 请不要建议我使用迭代器 - 我知道这通常是解决这些问题的方法.
  • 我不介意矢量必须复制数据,如果它稍后调整大小.
  • 这个问题特别令我感到困惑,因为C++已经移动了语义.如果我们可以从脚下拉出一个物体的存储器,为什么不能用我们自己的力量推?

小智 12

您可以.

你写的是std::vector<T>,但std::vector需要两个模板参数,而不只是一个.第二个模板参数指定要使用的分配器类型,并且vector构造函数具有允许传入该分配器类型的自定义实例的重载.

因此,您需要做的就是编写一个尽可能使用您自己的内部缓冲区的分配器,并在您自己的内部缓冲区已满时回退询问默认分配器.

默认的分配器不可能希望处理它,因为它不知道可以释放哪些内存位,哪些不可以.


一个示例有状态分配器,其内部缓冲区包含不应被向量覆盖的已构造元素,包括演示大问题:

struct my_allocator_state {
    void *buf;
    std::size_t len;
    bool bufused;
    const std::type_info *type;
};

template <typename T>
struct my_allocator {
    typedef T value_type;

    my_allocator(T *buf, std::size_t len)
        : def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, len, false, &typeid(T) })) { }

    template <std::size_t N>
    my_allocator(T(&buf)[N])
        : def(), state(std::make_shared<my_allocator_state, my_allocator_state>({ buf, N, false, &typeid(T) })) { }

    template <typename U>
    friend struct my_allocator;

    template <typename U>
    my_allocator(my_allocator<U> other)
        : def(), state(other.state) { }

    T *allocate(std::size_t n)
    {
        if (!state->bufused && n == state->len && typeid(T) == *state->type)
        {
            state->bufused = true;
            return static_cast<T *>(state->buf);
        }
        else
            return def.allocate(n);
    }

    void deallocate(T *p, std::size_t n)
    {
        if (p == state->buf)
            state->bufused = false;
        else
            def.deallocate(p, n);
    }

    template <typename...Args>
    void construct(T *c, Args... args)
    {
        if (!in_buffer(c))
            def.construct(c, std::forward<Args>(args)...);
    }

    void destroy(T *c)
    {
        if (!in_buffer(c))
            def.destroy(c);
    }

    friend bool operator==(const my_allocator &a, const my_allocator &b) {
        return a.state == b.state;
    }

    friend bool operator!=(const my_allocator &a, const my_allocator &b) {
        return a.state != b.state;
    }

private:
    std::allocator<T> def;
    std::shared_ptr<my_allocator_state> state;

    bool in_buffer(T *p) {
        return *state->type == typeid(T)
            && points_into_buffer(p, static_cast<T *>(state->buf), state->len);
    }
};

int main()
{
    int buf [] = { 1, 2, 3, 4 };
    std::vector<int, my_allocator<int>> v(sizeof buf / sizeof *buf, {}, buf);
    v.resize(3);
    v.push_back(5);
    v.push_back(6);
    for (auto &i : v) std::cout << i << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出:

1
2
3
4
6

push_back5装配到旧的缓冲区,因此施工被绕过.当6添加,新的内存分配,一切都开始充当正常.您可以通过向分配器添加一个方法来避免该问题,以指示从该点开始,不应再绕过构造.

points_into_buffer原来是最难写的部分,我从答案中省略了这一点.从我使用它的方式来看,预期的语义应该是显而易见的.请在此处查看我的问题,以便在我的答案中提供便携式实现,或者如果您的实现允许,请使用其他问题中的一个更简单的版本.

顺便说一下,我对某些实现如何使用rebind这种方式并不是很满意,因为没有避免存储运行时类型信息以及状态,但是如果你的实现不需要,你可以做一点通过使状态成为模板类(或嵌套类)也更简单.

  • 你可能能够修改分配器的`construct()`和`destroy()`来实现它,但它会非常混乱. (2认同)

zmb*_*mbq 6

简短的回答是向量不能使用你的缓冲区,因为它不是那样设计的.

这也是有道理的.如果向量没有分配自己的内存,那么在添加更多项目时如何调整缓冲区的大小?它分配了一个新缓冲区,但它与旧缓冲区有什么关系呢?同样适用于移动 - 如果向量不控制自己的缓冲区,它如何将此缓冲区控制到另一个实例?

  • 那么如果向量不知道你是如何得到它的,它怎么能重新分配你原来的“T*”呢?答案 - 它不能。并且破解一个分配器来跟踪它仍然没有给你任何方式来通知向量缓冲区已经填充。 (2认同)

ein*_*ica 5

这些天 - 您不再需要将 a 包装T*在 an 中std::vector,您可以用 an 包装它std::span(在 C++20 中;在此之前 - 使用gsl::span)。跨度为您提供了标准库容器的所有便利 - 实际上,基本上是std::vector排除大小更改的所有相关功能- 具有非常薄的包装器类。这就是你想要使用的,真的。

有关跨度的更多信息,请阅读: 什么是“跨度”,何时应该使用?