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

einpoklum

I have a T* addressing a buffer with len elements of type T. I need this data in the form of an std::vector<T>, for certain reasons. As far as I can tell, I cannot construct a vector which uses my buffer as its internal storage. Why is that?

Notes:

  • Please don't suggest I use iterators - I know that's usually the way around such issues.
  • I don't mind that the vector having to copy data around if it's resized later.
  • This question especially baffles me now that C++ has move semantics. If we can pull an object's storage from under its feet, why not be able to shove in our own?
user743382

You can.

You write about std::vector<T>, but std::vector takes two template arguments, not just one. The second template argument specifies the allocator type to use, and vector's constructors have overloads that allow passing in a custom instance of that allocator type.

So all you need to do is write an allocator that uses your own internal buffer where possible, and falls back to asking the default allocator when your own internal buffer is full.

The default allocator cannot possibly hope to handle it, since it would have no clue on which bits of memory can be freed and which cannot.


A sample stateful allocator with an internal buffer containing already-constructed elements that should not be overwritten by the vector, including a demonstration of a big gotcha:

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;
}

Output:

1
2
3
4
6

The push_back of 5 fits into the old buffer, so construction is bypassed. When 6 is added, new memory is allocated, and everything starts acting as normal. You could avoid that problem by adding a method to your allocator to indicate that from that point onward, construction should not be bypassed any longer.

points_into_buffer turned out to be the hardest part to write, and I've omitted that from my answer. The intended semantics should be obvious from how I'm using it. Please see my question here for a portable implementation in my answer there, or if your implementation allows it, use one of the simpler versions in that other question.

By the way, I'm not really happy with how some implementations use rebind in such ways that there is no avoiding storing run-time type info along with the state, but if your implementation doesn't need that, you could make it a bit simpler by making the state a template class (or a nested class) too.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Why can't I change objects in a vector?

From Dev

How can't I wrap a function (std::bind) into a namespaces?

From Dev

Why can't I move std::ofstream?

From Dev

Can std::vector<T>::iterator simply be T*?

From Dev

Why can't I wrap Await in parenthesis in a statement?

From Dev

Why can't I get my textblock to wrap

From Dev

Why can't I use std::get<0> in std::transform?

From Dev

How to properly wrap std::vector<std::size_t> with SWIG for Python? Problems with std::size_t

From Dev

Can I use std::vector<std::vector<T>> to represent two dimensional arrays in C++?

From Java

Why can't GCC assume that std::vector::size won't change in this loop?

From Dev

Why can't I add items into my vector?

From Dev

Why can't I create a vector of threads on the fly like this

From Dev

Why can't I convert a vector into a matrix with 'as.matrix' function?

From Dev

Why can't I access directly in vector iterator?

From Dev

Why can't I push_back to a vector of const elements?

From Dev

Why can't i construct a vector by passing temporary input iterator?

From Dev

Why I can't see any ouput iterating this vector?

From Dev

Why can't I set an iterator on a list in a vector?

From Java

why can't I construct an std::span from iterators?

From Dev

Why I can't desalinize std::array like this?

From Dev

Why can't I use operator bool() for std::ofstream

From Dev

Why can't I use the gtest ValuesIn generator with a std::map?

From Dev

Why can't I pass an rvalue std::stringstream by value to a function?

From Dev

Why can't I specialize std::tuple_element?

From Dev

Why can't I use std::copy in my copy constructor?

From Dev

Why can't i use std:cin as an argument

From Dev

Why can't I use auto with std::thread?

From Dev

Why can't I use this parameter for Compare of std::sort()?

From Dev

Why can't I return a nullptr std::weak_ptr?

Related Related

  1. 1

    Why can't I change objects in a vector?

  2. 2

    How can't I wrap a function (std::bind) into a namespaces?

  3. 3

    Why can't I move std::ofstream?

  4. 4

    Can std::vector<T>::iterator simply be T*?

  5. 5

    Why can't I wrap Await in parenthesis in a statement?

  6. 6

    Why can't I get my textblock to wrap

  7. 7

    Why can't I use std::get<0> in std::transform?

  8. 8

    How to properly wrap std::vector<std::size_t> with SWIG for Python? Problems with std::size_t

  9. 9

    Can I use std::vector<std::vector<T>> to represent two dimensional arrays in C++?

  10. 10

    Why can't GCC assume that std::vector::size won't change in this loop?

  11. 11

    Why can't I add items into my vector?

  12. 12

    Why can't I create a vector of threads on the fly like this

  13. 13

    Why can't I convert a vector into a matrix with 'as.matrix' function?

  14. 14

    Why can't I access directly in vector iterator?

  15. 15

    Why can't I push_back to a vector of const elements?

  16. 16

    Why can't i construct a vector by passing temporary input iterator?

  17. 17

    Why I can't see any ouput iterating this vector?

  18. 18

    Why can't I set an iterator on a list in a vector?

  19. 19

    why can't I construct an std::span from iterators?

  20. 20

    Why I can't desalinize std::array like this?

  21. 21

    Why can't I use operator bool() for std::ofstream

  22. 22

    Why can't I use the gtest ValuesIn generator with a std::map?

  23. 23

    Why can't I pass an rvalue std::stringstream by value to a function?

  24. 24

    Why can't I specialize std::tuple_element?

  25. 25

    Why can't I use std::copy in my copy constructor?

  26. 26

    Why can't i use std:cin as an argument

  27. 27

    Why can't I use auto with std::thread?

  28. 28

    Why can't I use this parameter for Compare of std::sort()?

  29. 29

    Why can't I return a nullptr std::weak_ptr?

HotTag

Archive