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

时间:2015-01-01 11:26:47

标签: c++ c++11 vector wrapper

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

注意:

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

3 个答案:

答案 0 :(得分: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;
}

输出:

1
2
3
4
6

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

6被证明是最难写的部分,我从答案中省略了这一点。从我使用它的方式来看,预期的语义应该是显而易见的。请在我的答案中查看my question here的便携式实现,或者如果您的实现允许,请使用其他问题中的一个更简单的版本。

顺便说一句,我对某些实现如何使用points_into_buffer以及不存在避免将运行时类型信息与状态一起存储的方式感到不满意,但是如果您的实现没有#&# 39; t需要它,你可以通过使状态成为模板类(或嵌套类)来使它更简单。

答案 1 :(得分:6)

简短的回答是,矢量无法使用您的缓冲区,因为它不是那样设计的。

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

答案 2 :(得分:2)

这几天-您不再需要,可以用T*包装std::span(在C ++ 20中;在此之前-使用gsl::span)。跨度为您提供了标准库容器的所有便利-实际上,std::vector的所有相关功能(不包括大小更改)都具有非常薄的包装器类。确实是您要使用的。

有关跨度的更多信息,请阅读:What is a "span" and when should I use one?