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

Why can't I wrap a T* in an std::vector<T>?

我有一个 T* 使用类型 Tlen 个元素寻址缓冲区。出于某些原因,我需要 std::vector<T> 形式的数据。据我所知,我无法构建一个使用我的缓冲区作为其内部存储的向量。这是为什么?

备注:

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

这也有道理。如果 vector 不分配自己的内存,那么当添加更多项时它如何调整缓冲区的大小?它分配了一个新缓冲区,但是它对旧缓冲区有什么作用呢?这同样适用于移动——如果向量不控制它自己的缓冲区,它怎么能把这个缓冲区的控制权交给另一个实例呢?

可以。

您写的是 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;
}

输出:

1
2
3
4
6

5push_back 适合旧缓冲区,因此构造被绕过。添加 6 时,分配新内存,一切开始正常运行。您可以通过向分配器添加一个方法来避免该问题,以指示从那时起,不应再绕过构造。

points_into_buffer 结果是最难写的部分,我在回答中省略了它。从我的使用方式来看,预期的语义应该是显而易见的。请在我的回答中查看 的可移植实现,或者如果您的实现允许,请在另一个问题中使用一个更简单的版本。

顺便说一句,我对某些实现如何以无法避免存储 运行 时间类型信息和状态的方式使用 rebind 不太满意,但是如果您的实现不需要它,您也可以通过将状态设为模板 class(或嵌套 class)来使其更简单。

最近 - 您不再需要T* 包裹在 std::vector 中,您可以将其包裹在 std::span 中(在 C++20 中;在此之前 - 使用 gsl::span)。 span 为您提供标准库容器的所有便利 - 事实上,基本上 std::vector 的所有相关功能不包括大小更改 - 具有非常薄的包装器 class。这就是你想要使用的,真的。

有关跨度的更多信息,请阅读: