Использование std :: vector для просмотра сырой памяти - PullRequest
71 голосов
/ 10 февраля 2020

Я использую внешнюю библиотеку, которая в какой-то момент дает мне необработанный указатель на массив целых чисел и размер.

Теперь я хотел бы использовать std::vector для доступа и изменения этих значений вместо того, чтобы обращаться к ним с помощью необработанных указателей.

Вот примерный пример, объясняющий суть:

size_t size = 0;
int * data = get_data_from_library(size);   // raw data from library {5,3,2,1,4}, size gets filled in

std::vector<int> v = ????;                  // pseudo vector to be used to access the raw data

std::sort(v.begin(), v.end());              // sort raw data in place

for (int i = 0; i < 5; i++)
{
  std::cout << data[i] << "\n";             // display sorted raw data 
}

Ожидаемый результат:

1
2
3
4
5

Причина в том, что мне нужно применить алгоритмы из <algorithm> (сортировка, замена элементов и т.д. c.) к этим данным.

С другой стороны, изменение размера этого вектора никогда не изменится, поэтому push_back, erase, insert не требуются для работы с этим вектором.

Я мог бы построить вектор на основе данных из библиотеки, использовать модифицировать этот вектор и копировать данные обратно в библиотеку, но это были бы две полные копии, которых я бы хотел избежать, поскольку набор данных мог бы быть очень большим.

Ответы [ 10 ]

63 голосов
/ 10 февраля 2020

C ++ 20-х std::span

Если вы можете использовать C ++ 20, вы можете использовать std::span, который является парой указатель-длина, которая дает пользователю взгляд на непрерывную последовательность элементов. Это своего рода std::string_view, и хотя оба std::span и std::string_view являются несобственными представлениями, std::string_view является представлением только для чтения.

Из документов:

Диапазон шаблона класса описывает объект, который может ссылаться на непрерывную последовательность объектов с первым элементом последовательности в нулевой позиции. Интервал может иметь либо обычный c экстент, в этом случае число элементов в последовательности известно и закодировано в типе, либо динамический c экстент.

Таким образом, следующее будет работать:

#include <span>
#include <iostream>
#include <algorithm>

int main() {
    int data[] = { 5, 3, 2, 1, 4 };
    std::span<int> s{data, 5};

    std::sort(s.begin(), s.end());

    for (auto const i : s) {
        std::cout << i << "\n";
    }

    return 0;
}

Проверьте это live

Поскольку std::span в основном пара указатель - длина, вы также можете использовать ее следующим образом:

size_t size = 0;
int *data = get_data_from_library(size);
std::span<int> s{data, size};

Примечание: Не все компиляторы поддерживают std::span. Проверьте поддержку компилятора здесь .

ОБНОВЛЕНИЕ

Если вы не можете использовать C ++ 20, вы можете использовать gsl::span, которая в основном является базовой версией стандарта C ++ std::span.

C ++ 11 решение

Если вы ограничены стандартом C ++ 11, вы можете попробовать реализовать ваш собственный простой span класс:

template<typename T>
class span {
   T* ptr_;
   std::size_t len_;

public:
    span(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];
    }

    std::size_t size() const noexcept {
        return len_;
    }

    T* begin() noexcept {
        return ptr_;
    }

    T* end() noexcept {
        return ptr_ + len_;
    }
};

Проверьте версию C ++ 11 live

60 голосов
/ 10 февраля 2020

Проблема в том, что std::vector должен сделать копию элементов из массива, с которым вы его инициализируете, так как он владеет объектами, которые он содержит.

Чтобы избежать этого, вы можете использовать объект slice для массива (т. Е. Аналогично тому, что std::string_view соответствует std::string). Вы можете написать собственную реализацию шаблона класса array_view, экземпляры которой создаются путем получения необработанного указателя на первый элемент массива и длины массива:

#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 не хранит массив; он просто содержит указатель на начало массива и длину этого массива. Следовательно, array_view объекты дешевы в создании и копировании.

Поскольку array_view предоставляет функции-члены begin() и end(), вы можете использовать стандартные библиотечные алгоритмы (например, std::sort, std::find, std::lower_bound, et c.) На нем:

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

Выход:

1 2 3 4 5

Использование std::span (или gsl::span) вместо

Приведенная выше реализация раскрывает концепцию объектов среза . Однако, начиная с C ++ 20, вы можете напрямую использовать std::span. В любом случае вы можете использовать gsl::span начиная с C ++ 14.

29 голосов
/ 10 февраля 2020

Поскольку библиотека алгоритмов работает с итераторами, вы можете сохранить массив.

Для указателей и известной длины массива

Здесь вы можете использовать необработанные указатели в качестве итераторов. Они поддерживают все операции, которые поддерживает итератор (инкремент, сравнение на равенство, значение, и т. Д. c ...):

#include <iostream>
#include <algorithm>

int *get_data_from_library(int &size) {
    static int data[] = {5,3,2,1,4}; 

    size = 5;

    return data;
}


int main()
{
    int size;
    int *data = get_data_from_library(size);

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

    for (int i = 0; i < size; i++)
    {
        std::cout << data[i] << "\n";
    }
}

data указывает на элемент первого массива, как итератор, возвращаемый begin() и data + size указывают на элемент после последнего элемента массива, как итератор, возвращаемый end().

Для массивов

Здесь вы можете использовать std::begin() и std::end()

#include <iostream>
#include <algorithm>

int main()
{
    int data[] = {5,3,2,1,4};         // raw data from library

    std::sort(std::begin(data), std::end(data));    // sort raw data in place

    for (int i = 0; i < 5; i++)
    {
        std::cout << data[i] << "\n";   // display sorted raw data 
    }
}

Но имейте в виду, что это работает, только если data не затухает до указателя, потому что тогда информация о длине пропадает.

13 голосов
/ 10 февраля 2020

Вы можете получить итераторы для необработанных массивов и использовать их в алгоритмах:

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

Если вы работаете с необработанными указателями (ptr + size), то вы можете использовать следующую технику:

    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: Однако приведенный выше пример имеет плохой дизайн. Библиотека возвращает нам необработанный указатель, и мы не знаем, где расположен основной буфер и кто должен его освобождать.

Обычно вызывающая сторона предоставляет буферизованную функцию для заполнения данных. В этом случае мы можем предварительно выделить вектор и использовать его базовый буфер:

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

При использовании C ++ 11 или выше мы можем даже сделать get_data_from_library () для возврата вектора. Благодаря операциям перемещения не будет копии памяти.

9 голосов
/ 10 февраля 2020

Теперь я хотел бы использовать std :: vector для доступа и изменения этих значений вместо

Вы не можете. Это не то, для чего std::vector. std::vector управляет своим собственным буфером, который всегда получается из распределителя. Он никогда не вступает во владение другим буфером (кроме другого вектора того же типа).

С другой стороны, вам также не нужно, потому что ...

Причина заключается в том, что мне нужно применить алгоритмы (сортировка, замена элементов и т. д. c.) к этим данным.

Эти алгоритмы работают на итераторах. Указатель является итератором массива. Вам не нужен вектор:

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

В отличие от шаблонов функций в <algorithm>, некоторые инструменты, такие как range-for, std::begin / std::end и C ++ 20, не работают с только пара итераторов, хотя они работают с контейнерами, такими как векторы. Можно создать класс-оболочку для iterator + size, который ведет себя как диапазон и работает с этими инструментами. C ++ 20 введет такую ​​оболочку в стандартную библиотеку: std::span.

9 голосов
/ 10 февраля 2020

Вы не можете сделать это с std::vector без копирования. std::vector владеет указателем, находящимся под капотом, и выделяет пространство через предоставленный распределитель.

Если у вас есть доступ к компилятору, который поддерживает C ++ 20, вы можете использовать std: : span , который был построен именно для этой цели. Он оборачивает указатель и размер в «контейнер», который имеет интерфейс контейнера C ++.

Если нет, вы можете использовать gsl :: span , на котором основана стандартная версия .

Если вы не хотите импортировать другую библиотеку, вы можете легко реализовать ее самостоятельно, в зависимости от того, какую функциональность вы хотите иметь.

7 голосов
/ 10 февраля 2020

Помимо других хороших предложений о std::span, поступающих в и gsl:span, включая ваш собственный (легкий) класс span до тех пор, это уже достаточно просто (не стесняйтесь копировать ):

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");

Особо следует отметить также библиотеку диапазонов усиления , если вас интересует более общая концепция диапазона c: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html.

Концепции диапазона также появятся в

6 голосов
/ 10 февраля 2020

Вы на самом деле могли бы почти использовать std::vector для этого, злоупотребляя пользовательской функцией распределителя, чтобы возвращать указатель на память, которую вы хотите просмотреть. Это не гарантировалось бы стандартом для работы (заполнение, выравнивание, инициализация возвращаемых значений; вам пришлось бы прилагать усилия при назначении начального размера, а для не примитивов вам также нужно было бы взломать ваши конструкторы ), но на практике я ожидал бы, что он даст достаточно настроек.

Никогда так не делал. Это уродливо, удивительно, нахально и ненужно. Алгоритмы стандартной библиотеки уже предназначены для работы как с необработанными массивами, так и с векторами. См. Другие ответы для деталей этого.

4 голосов
/ 11 февраля 2020

Как уже отмечали другие, std::vector должен владеть основной памятью (если не считать переписки с пользовательским распределителем), поэтому его нельзя использовать.

Другие также рекомендовали диапазон c ++ 20, однако очевидно, что требуется c ++ 20.

Я бы порекомендовал span-lite span. Процитируем его субтитры:

span lite - C ++ 20-подобный span для C ++ 98, C ++ 11 и более поздних версий в библиотеке из одного файла только для заголовков

Это обеспечивает не владеющее и изменяемое представление (так как вы можете изменять элементы и их порядок, но не вставлять их), и, как говорится в цитате, не имеет зависимостей и работает на большинстве компиляторов.

Ваш пример:

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

Печать

1
2
3
4
5

Это также имеет дополнительный потенциал, если однажды вы переключитесь на c ++ 20, вы просто сможете заменить это nonstd::span с std::span.

3 голосов
/ 10 февраля 2020

Вы можете использовать std::reference_wrapper, доступный с C ++ 11:

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

int main()
{
    int src_table[] = {5, 4, 3, 2, 1, 0};

    std::vector< std::reference_wrapper< int > > dest_vector;

    std::copy(std::begin(src_table), std::end(src_table), std::back_inserter(dest_vector));
    // if you don't have the array defined just a pointer and size then:
    // std::copy(src_table_ptr, src_table_ptr + size, std::back_inserter(dest_vector));

    std::sort(std::begin(dest_vector), std::end(dest_vector));

    std::for_each(std::begin(src_table), std::end(src_table), [](int x) { std::cout << x << '\n'; });
    std::for_each(std::begin(dest_vector), std::end(dest_vector), [](int x) { std::cout << x << '\n'; });
}
...