25

My question is fairly simple and I am quite surprised I can't find anything related. Probably it is easy or totally stupid (or I can't search).

As the title says, is it possible to use std::vector on already allocated memory, so it does not allocate new elements from start but uses what is given. I would imagine it as something like:

T1 *buffer = new T1[some_size];
std::vector<T2> v(buffer, some_size); // <- ofc doesn't work

The opposite is quite simple and (maybe not pretty but) works:

std::vector<T2> v(some_size);
T1 *buffer = &v[0];

The storage is guaranteed to be continuous, so it is as safe as an iterator.

My motivation is quite simple. I pass around some raw memory data, i.e. bytes, and since I know their interpretation at some other places I would like to convert them back to something meaningful. I could do a reinterpret_cast and use normal c-style array, but I prefer c++ facilities.

I get the feeling this should be safe given that we give up ownership of buffer to vector, because it needs to be able to reallocate.

Cœur
  • 37,241
  • 25
  • 195
  • 267
luk32
  • 15,812
  • 38
  • 62
  • 1
    `new (buffer) std::array` if you don't mind using an array instead of a vector – Brian Bi Feb 20 '14 at 19:28
  • @BrianBi I was thinking about placement new, but I thought it is dangerous, because I don't if the class memory layout is guaranteed. Also is the sizeof(std::array) guaranteed to be `sizeof(T2)*some_size`, otherwise it won't fit the `buffer`, this also implies that there would need to be zero-byte overhead on `std::array`. – luk32 Feb 20 '14 at 19:34
  • 1
    If `T1` can be moved efficiently, it would be MUCH MUCH simpler to move the elements into a vector: `std::vector v(std::make_move_iterator(buffer), std::make_move_iterator(buffer + size));` and free the buffer. – Casey Feb 20 '14 at 19:38
  • The standard says, "The elements of an `array` are stored contiguously" – Brian Bi Feb 20 '14 at 19:38
  • @Casey No it cannot, I just want to reinterpret what is inside buffer. I wanted to use `vector` facilities, but is seems like it is not that easy. I guess the trouble supporting it could be, that the `buffer` might point to a part of a larger buffer, and then reallocation would be impossible. So probably custom container, or allocator is the only way to go. I thought there is something known for this. – luk32 Feb 20 '14 at 19:40
  • @BrianBi This does not change anything, does it? Same goes for vector, as I said it. It makes the "conversion" in the opposite direction possible. – luk32 Feb 20 '14 at 19:43
  • @luk32 On the contrary, a `std::array` is actually just a class that contains an array and a bunch of member functions for accessing it. – Brian Bi Feb 20 '14 at 19:46
  • @BrianBi You just said "The elements of an array are stored contiguously". This is also true for a `vector`, but it does not have zero memory overhead over data stored. You also did not say that it is guaranteed that `array` does. – luk32 Feb 20 '14 at 19:48
  • @luk32: See http://www.cplusplus.com/reference/array/array/ which says "Internally, an array does not keep any data other than the elements it contains (not even its size, which is a template parameter, fixed on compile time)." I'm too lazy to dig up the reference from the standard. – Brian Bi Feb 20 '14 at 19:51
  • You need to differentiate between allocated (raw) memory and array of `T` objects. You can use allocated uninitialized memory through a custom allocator as the answers describe. You cannot transfer ownership of actual `T` objects already constructed inside that memory. Your question seems to be attempting the latter but you have accepted an answer that does something different. Which one are you actually trying to do? – patatahooligan Feb 06 '18 at 15:39
  • Sorry for late reply, but it was 4 years ago. It does seem that intention was to repack an array into a vector, but it is hard to see whether I meant that the contents of the array where simply bytes, or instantiated objects. =[ sorry about that. By simply bytes I mean, the binary data made sense, something like buffer created by using `memcpy` on objects of a trivially copyable type. – luk32 Feb 13 '18 at 13:47
  • @luk32 Now in C++20 there is `std::span` with which you can interpret an existing buffer as byte array and also provides `std::vector` like facilities. – Mandeep Singh Feb 08 '21 at 10:53
  • @MandeepSingh This certainly is related, but in my original problem I was interested in giving up ownership to the `vector` this might not have been spelled out clearly enough. `span` is kind of a "`const vector`". I.e. you cant mutate container itself. It cannot behave like `vector` with initialized [data](https://en.cppreference.com/w/cpp/container/vector/data) beyond element access. – luk32 Feb 08 '21 at 23:10

3 Answers3

27

Like this.. Containers in the standard usually take an allocator. Using c++11's allocator traits, it is very easy to create an allocator as you don't have to have all the members in the allocator. However if using an older version of C++, you will need to implement each member and do the rebinding as well!

For Pre-C++11, you can use the following:

#include <iterator>
#include <vector>
#include <iostream>

template<typename T>
class PreAllocator
{
    private:
        T* memory_ptr;
        std::size_t memory_size;

    public:
        typedef std::size_t size_type;
        typedef ptrdiff_t difference_type;
        typedef T* pointer;
        typedef const T* const_pointer;
        typedef T& reference;
        typedef const T& const_reference;
        typedef T value_type;


        PreAllocator(T* memory_ptr, std::size_t memory_size) throw() : memory_ptr(memory_ptr), memory_size(memory_size) {};
        PreAllocator (const PreAllocator& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

        template<typename U>
        PreAllocator (const PreAllocator<U>& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

        template<typename U>
        PreAllocator& operator = (const PreAllocator<U>& other) {return *this;}
        PreAllocator<T>& operator = (const PreAllocator& other) {return *this;}
        ~PreAllocator() {}

        pointer address (reference value) const {return &value;}
        const_pointer address (const_reference value) const {return &value;}

        pointer allocate (size_type n, const void* hint = 0) {return memory_ptr;}
        void deallocate (T* ptr, size_type n) {}

        void construct (pointer ptr, const T& val) {new (ptr) T (val);}

        template<typename U>
        void destroy (U* ptr) {ptr->~U();}
        void destroy (pointer ptr) {ptr->~T();}

        size_type max_size() const {return memory_size;}

        template<typename U>
        struct rebind
        {
            typedef PreAllocator<U> other;
        };
};

int main()
{
    int my_arr[100] = {0};
    std::vector<int, PreAllocator<int> > my_vec(PreAllocator<int>(&my_arr[0], 100));
    my_vec.push_back(1024);
    std::cout<<"My_Vec[0]: "<<my_vec[0]<<"\n";
    std::cout<<"My_Arr[0]: "<<my_arr[0]<<"\n";

    int* my_heap_ptr = new int[100]();
    std::vector<int, PreAllocator<int> > my_heap_vec(PreAllocator<int>(&my_heap_ptr[0], 100));
    my_heap_vec.push_back(1024);
    std::cout<<"My_Heap_Vec[0]: "<<my_heap_vec[0]<<"\n";
    std::cout<<"My_Heap_Ptr[0]: "<<my_heap_ptr[0]<<"\n";

    delete[] my_heap_ptr;
    my_heap_ptr = NULL;
}

For C++11, you can use the following:

#include <cstdint>
#include <iterator>
#include <vector>
#include <iostream>

template <typename T>
class PreAllocator
{
    private:
        T* memory_ptr;
        std::size_t memory_size;

    public:
        typedef std::size_t     size_type;
        typedef T*              pointer;
        typedef T               value_type;

        PreAllocator(T* memory_ptr, std::size_t memory_size) : memory_ptr(memory_ptr), memory_size(memory_size) {}

        PreAllocator(const PreAllocator& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

        template<typename U>
        PreAllocator(const PreAllocator<U>& other) throw() : memory_ptr(other.memory_ptr), memory_size(other.memory_size) {};

        template<typename U>
        PreAllocator& operator = (const PreAllocator<U>& other) { return *this; }
        PreAllocator<T>& operator = (const PreAllocator& other) { return *this; }
        ~PreAllocator() {}


        pointer allocate(size_type n, const void* hint = 0) {return memory_ptr;}
        void deallocate(T* ptr, size_type n) {}

        size_type max_size() const {return memory_size;}
};

int main()
{
    int my_arr[100] = {0};
    std::vector<int, PreAllocator<int>> my_vec(0, PreAllocator<int>(&my_arr[0], 100));
    my_vec.push_back(1024);
    std::cout<<"My_Vec[0]: "<<my_vec[0]<<"\n";
    std::cout<<"My_Arr[0]: "<<my_arr[0]<<"\n";

    int* my_heap_ptr = new int[100]();
    std::vector<int, PreAllocator<int>> my_heap_vec(0, PreAllocator<int>(&my_heap_ptr[0], 100));
    my_heap_vec.push_back(1024);
    std::cout<<"My_Heap_Vec[0]: "<<my_heap_vec[0]<<"\n";
    std::cout<<"My_Heap_Ptr[0]: "<<my_heap_ptr[0]<<"\n";

    delete[] my_heap_ptr;
    my_heap_ptr = nullptr;
}

Notice the difference between the two allocators! This will work with both heap buffers/arrays and stack buffer/arrays. It will also work with most containers. It is safer to use the Pre-C++11 version because it will be backwards compatible and work with more containers (ie: std::List).

You can just place the allocator in a header and use it as much as you want in any projects. It is good if you want to use SharedMemory or any buffer that is already allocated.

WARNING: DO NOT use the same buffer for multiple containers at the same time! A buffer can be reused but just make sure no two containers use it at the same time.

Example:

int my_arr[100] = {0};
std::vector<int, PreAllocator<int> > my_vec(PreAllocator<int>(&my_arr[0], 100));
std::vector<int, PreAllocator<int> > my_vec2(PreAllocator<int>(&my_arr[0], 100));

my_vec.push_back(1024);
my_vec2.push_back(2048);

std::cout<<"My_Vec[0]: "<<my_vec[0]<<"\n";
std::cout<<"My_Arr[0]: "<<my_arr[0]<<"\n";

The output of the above is 2048! Why? Because the last vector overwrote the values of the first vector since they share the same buffer.

Brandon
  • 22,723
  • 11
  • 93
  • 186
  • 2
    Wow, now I kinda feel bad for asking, I knew it could be done with custom allocator, but I felt like it was too much work for my purpose. I think it should also fail when reallocation should happen, but the passed pointer does not point to the start of the allocated block of memory. I do not really care, because I will probably be careful enough. Btw: "You can just place the allocator in a header and use it as much as you want in any projects or wherever." I treat it as giving rights to freely use the code for any purposes =) – luk32 Feb 20 '14 at 20:47
  • Well the code is posted on stackoverflow. There isn't any license attached. I wrote it myself so yes it is free. There isn't much you can license in it anyway. It's just an allocator. Anyway, it already fails on reallocation. For example, if your buffer is 100 bytes and you try to resize or reserve 101 bytes, it will throw `std::length_error`. However, if the buffer given is 100 bytes and you resize 50, it is fine. It won't do anything except use 50/100 bytes. It is better to just use reserve if the buffer already contains data because resize will overwrite it with default values. – Brandon Feb 20 '14 at 20:51
  • Btw, if you do not want it to throw `std::length_error` on invalid allocation, remove `memory_size` and change `max_size` function to return: `std::size_t(-1);` – Brandon Feb 20 '14 at 20:54
  • I think is fine to throw and exception I find it the most correct behaviour in this situation. I am fine with the container being not "movable". – luk32 Feb 20 '14 at 22:02
  • It is moveable in both C++11 and Pre-C++11. For Pre-C++11, just set the other vector to use the same buffer and then do `newvector.reserve(oldvector.size())` and destroy the old vector. For C++11 just use `auto newvector = std::move(oldvector)`. – Brandon Feb 20 '14 at 22:46
  • 1
    I mean "movable", used quotation marks on purpose. I mean moved in physical memory - reallocated, not semantically. – luk32 Feb 20 '14 at 22:48
  • Elements pushed into the vector are constructed on top of the array's elements without destroying the latter. This is undefined behavior and unlikely to play well for non-trivially-destructible types (which the question does not exclude). – patatahooligan Feb 05 '18 at 21:48
  • @patatahooligan; I don't get what you mean. I've tested this and if you tried to replace an element in the vector, it does get destroyed first.. I've tested with regular `std::allocator` and the above allocator in and both cases, they print the exact same outputs.. https://ideone.com/fiDmIk vs. https://ideone.com/j25WfU – Brandon Feb 06 '18 at 02:00
  • In your ideone example you are using `char[]` as uninitialized storage. The question and your answer is about using a `vector` on top of already initialized `T[]`. I've modified your code to create a simpler example that illustrates the problem [here](https://ideone.com/e47qSS). Notice that two constructors are called on the same storage without destroying the stored object in between. Then two destructors are called consecutively on the same storage. Both of these are UB. – patatahooligan Feb 06 '18 at 11:01
  • @patatahooligan; Notice that OP is using `T*` as uninitialized storage.. not `T[size];` There's a big difference there and anyone that doesn't see that deserves the UB. Hence the reason I used `char[]` as the backing.. so there's no construction of objects that won't be used and would cause no issues. Of course if I do your example it's going to be UB. The question is not about using `T[]` it's about using `T*`. Sure you can modify the code to cover `T[]`. – Brandon Feb 06 '18 at 13:55
  • No, OP is not using uninitialized storage. Uninitialized storage would be `malloc` or `char[]`. Instead they are using `new T[]` which constructs objects. Moreover, your answer is different from your ideone code as it is not using `char[]`. In practice, it will probably work for trivially destructible `int`s, but I doubt the standard allows it and it is definitely misleading to OP who is going to use it on arbitrary `T`. Lastly, you cannot simply adapt the code to use `T[]` because the constructor-destructor problem would remain unless explicitly handled. – patatahooligan Feb 06 '18 at 14:07
  • If you have data in the heap memory array that you want to be the data in the vector, how do you do that? The only viable constructor seems to be the fill constructor but that will overwrite the content of the array. – msc Feb 15 '20 at 13:36
  • To answer my own question. It looks like using reserve() works after constructing the vector with the empty container constructor. I need more testing to be certain. Note that the initial 0 arguments to the constructors shown in the C++11 version give compile errors. Removing them gives the empty container constructor. – msc Feb 15 '20 at 14:03
  • reserve() is working for me with the proviso that size() returns 0 which has required some special handling in my app. There seems to be no way to set the size that doesn't result in overwriting the underlying memory array. – msc Feb 19 '20 at 02:16
  • I know this is a rather old post, but doesn't your example with `std::vector > my_vec(PreAllocator(&my_arr[0], 100));` end in undefined behavior? I've run the code with the Visual Studio 2019 implementation and at least this implementation is rebinding the provided allocator to allocate an element of type `struct std::_Container_proxy`. Now this should be a huge problem, since you've only provided memory to store your 100 `int`'s. Am I missing something? – 0xbadf00d Jan 20 '21 at 15:36
2

Yes, std::vector takes a custom allocator as a template parameter which can achieve what you want.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • 6
    OK I guess I forgot to add, "If so, how do I do this." =) – luk32 Feb 20 '14 at 19:26
  • 1
    @luk32 now that you know what it's called, you can search for a solution that fits your needs. ;) I haven't written a custom allocator for std containers yet so I don't want to set you on the wrong track. – Luchian Grigore Feb 20 '14 at 19:27
  • Well, I know there is a template parameter for Allocator. But I got the feeling I would need to write my own. Which seems a not-so-easy job. I have read cppreference on Allocator. Call me stupid, but I tried to find something on it. – luk32 Feb 20 '14 at 19:32
  • Here is one example that looks reasonable: http://blogs.msdn.com/b/calvin_hsia/archive/2010/03/16/9979985.aspx – i_am_jorf Feb 20 '14 at 19:32
1

If you look at the docs for std::vector you will see the second template parameter is a custom allocator:

template < class T, class Alloc = allocator<T> > class vector;

You can define your own std::allocator that returns whatever memory you want. This may be a lot more work than is worth it for your purposes though.

You cannot just pass in a pointer to random memory, however. You would have problems like, what if the vector needs to grow beyond the size of your initial buffer?

If you just want to operate on raw bytes sometimes, and vectors at other times, I would write helper functions to convert between the two, and just convert between the two when needed. If this causes performance issues (which you should measure), then custom allocator is your next course of action.

i_am_jorf
  • 53,608
  • 15
  • 131
  • 222
  • I figured out that it is possible to write a custom allocator (maybe I should have stated it explicitly), but that is not very feasible to me. It just felt quite stupid to be forced to copy buffer into memory when it looks like underlying container should be at least compatible with simple c-style array, and a `reinterpret_cast` would do the trick. – luk32 Feb 20 '14 at 19:38