Using std::vector as view on to raw memory

后端 未结 10 1573
感动是毒
感动是毒 2021-01-31 13:34

I\'m using a external library which at some point gives me a raw pointer to an array of integers and a size.

Now I\'d like to use std::vector to access and

相关标签:
10条回答
  • 2021-01-31 13:41

    You actually could almost use std::vector for this, by abusing the custom allocator functionality to return a pointer to the memory you want to view. That wouldn't be guaranteed by the standard to work (padding, alignment, initialization of the returned values; you'd have to take pains when assigning the initial size, and for non-primitives you'd also need to hack up your constructors), but in practice I would expect it to given enough tweaks.

    Never ever ever do that. It's ugly, surprising, hacky, and unnecessary. The standard library's algorithms are already designed to work as well with raw arrays as with vectors. See the other answers for details of that.

    0 讨论(0)
  • 2021-01-31 13:42

    As others have pointed out, std::vector must own the underlying memory (short of messing with a custom allocator) so can't be used.

    Others have also recommended c++20's span, however obviously that requires c++20.

    I would recommend the span-lite span. To quote it's subtitle:

    span lite - A C++20-like span for C++98, C++11 and later in a single-file header-only library

    It provides a non-owning and mutable view (as in you can mutate elements and their order but not insert them) and as the quote says has no dependencies and works on most compilers.

    Your example:

    #include <algorithm>
    #include <cstddef>
    #include <iostream>
    
    #include <nonstd/span.hpp>
    
    static int data[] = {5, 1, 2, 4, 3};
    
    // For example
    int* get_data_from_library()
    {
      return data;
    }
    
    int main ()
    {
      const std::size_t size = 5;
    
      nonstd::span<int> v{get_data_from_library(), size};
    
      std::sort(v.begin(), v.end());
    
      for (auto i = 0UL; i < v.size(); ++i)
      {
        std::cout << v[i] << "\n";
      }
    }
    

    Prints

    1
    2
    3
    4
    5
    

    This also has the added upside if one day you do switch to c++20, you should just be able to replace this nonstd::span with std::span.

    0 讨论(0)
  • 2021-01-31 13:43

    Now I'd like to use std::vector to access and modify these values in place

    You cannot. That's not what std::vector is for. std::vector manages its own buffer, which is always acquired from an allocator. It never takes ownership of another buffer (except from another vector of same type).

    On the other hand, you also don't need to because ...

    The reason is that I need to apply algorithms from (sorting, swaping elements etc.) on that data.

    Those algorithms work on iterators. A pointer is an iterator to an array. You don't need a vector:

    std::sort(data, data + size);
    

    Unlike function templates in <algorithm>, some tools such as range-for,std::begin/std::end and C++20 ranges do not work with just a pair of iterators though, while they do work with containers such as vectors. It is possible to create a wrapper class for iterator + size that behaves as a range, and works with these tools. C++20 will introduce such wrapper into the standard library: std::span.

    0 讨论(0)
  • 2021-01-31 13:45

    You can get iterators on raw arrays and use them in algorithms:

        int data[] = {5,3,2,1,4};
        std::sort(std::begin(data), std::end(data));
        for (auto i : data) {
            std::cout << i << std::endl;
        }
    

    If you are working with raw pointers (ptr + size), then you can use the following technique:

        size_t size = 0;
        int * data = get_data_from_library(size);
        auto b = data;
        auto e = b + size;
        std::sort(b, e);
        for (auto it = b; it != e; ++it) {
            cout << *it << endl;
        }
    

    UPD: However, the above example is of bad design. The library returns us a raw pointer and we don't know where the underlying buffer is allocated and who is supposed to free it.

    Usually, the caller provides a buffered for the function to fill the data. In that case, we can preallocate the vector and use its underlying buffer:

        std::vector<int> v;
        v.resize(256); // allocate a buffer for 256 integers
        size_t size = get_data_from_library(v.data(), v.size());
        // shrink down to actual data. Note that no memory realocations or copy is done here.
        v.resize(size);
        std::sort(v.begin(), v.end());
        for (auto i : v) {
            cout << i << endl;
        }
    

    When using C++11 or above we can even make get_data_from_library() to return a vector. Thanks to move operations, there will be no memory copy.

    0 讨论(0)
  • 2021-01-31 13:47

    The problem is that std::vector has to make a copy of the elements from the array you initialize it with as it has the ownership of the objects it contains.

    To avoid this, you can use a slice object for an array (i.e., similar to what std::string_view is to std::string). You could write your own array_view class template implementation whose instances are constructed by taking a raw pointer to an array's first element and the array length:

    #include <cstdint>
    
    template<typename T>
    class array_view {
       T* ptr_;
       std::size_t len_;
    public:
       array_view(T* ptr, std::size_t len) noexcept: ptr_{ptr}, len_{len} {}
    
       T& operator[](int i) noexcept { return ptr_[i]; }
       T const& operator[](int i) const noexcept { return ptr_[i]; }
       auto size() const noexcept { return len_; }
    
       auto begin() noexcept { return ptr_; }
       auto end() noexcept { return ptr_ + len_; }
    };
    

    array_view doesn't store an array; it just holds a pointer to the beginning of the array and the length of that array. Therefore, array_view objects are cheap to construct and to copy.

    Since array_view provides the begin() and end() member functions, you can use the standard library algorithms (e.g., std::sort, std::find, std::lower_bound, etc.) on it:

    #define LEN 5
    
    auto main() -> int {
       int arr[LEN] = {4, 5, 1, 2, 3};
    
       array_view<int> av(arr, LEN);
    
       std::sort(av.begin(), av.end());
    
       for (auto const& val: av)
          std::cout << val << ' ';
       std::cout << '\n';
    }
    

    Output:

    1 2 3 4 5
    

    Use std::span (or gsl::span) instead

    The implementation above exposes the concept behind slice objects. However, since C++20 you can directly use std::span instead. In any case, you can use gsl::span since C++14.

    0 讨论(0)
  • 2021-01-31 13:50

    Besides the other good suggestion about std::span coming in c++20 and gsl:span, including your own (lightweight) span class until then is easy enough already (feel free to copy):

    template<class T>
    struct span {
        T* first;
        size_t length;
        span(T* first_, size_t length_) : first(first_), length(length_) {};
        using value_type = std::remove_cv_t<T>;//primarily needed if used with templates
        bool empty() const { return length == 0; }
        auto begin() const { return first; }
        auto end() const { return first + length; }
    };
    
    static_assert(_MSVC_LANG <= 201703L, "remember to switch to std::span");
    

    Of special note is also the boost range library boost-range if you are interested in the more generic range concept: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html.

    Range concepts will also arrive in c++20

    0 讨论(0)
提交回复
热议问题