Я использую внешнюю библиотеку, которая в какой-то момент дает мне необработанный указатель на массив целых чисел и размер.
Теперь я хотел бы использовать 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>
(сортировка, замена элементов и т.д.) к этим данным.
С другой стороны, размер этого вектора никогда не будет изменен, поэтому push_back
, erase
, insert
не обязаны работать с этим вектором.
Я мог бы построить вектор на основе данных из библиотеки, использовать модификацию этого вектора и копирование данных обратно в библиотеку, но это будет две полные копии, чего я хотел бы избежать, так как набор данных может быть очень большим.
Если вы можете использовать C++20, вы можете использовать std::span
, которая является парой указатель - длина, которая дает пользователю представление о непрерывной последовательности элементов. Это что-то вроде std::string_view
, и в то время как std::span
и std::string_view
являются неосновными видами, std::string_view
- это вид только для чтения.
Из документов:
Шаблон класса span описывает объект, который может относиться к сопрягаемая последовательность объектов с первым элементом последовательности в положении "ноль". Пролет может иметь либо статическую степень, в которой если количество элементов в последовательности известно и закодировано в тип, или динамическая степень.
Таким образом, сработает следующее:
#include <span>
#include <iostream>
#include <алгоритм>
int main() {
int data[] = { 5, 3, 2, 1, 4 };
std::span<int> s{data, 5};
std::sort(s.begin(), s.end());
для (auto const i : s) {
std::cout << i << "\n";
}
вернуть 0;
}
Проверьте это [вживую] [1]
Так как std::span
в основном является парой указатель - длина, то и ее можно использовать следующим образом:
size_t размер = 0;
int *data = get_data_from_library(size);
std::span<int> s{data, size};
Note: Не все компиляторы поддерживают std::span
. Проверьте поддержку компилятора здесь.
ДАТА
Если вы не можете использовать C++, вы можете использовать gsl::span
, который, по сути, является базовой версией std::span
стандарта C++.
Если вы ограничены стандартом C++11, то можете попробовать реализовать свой собственный простой класс span
:
шаблон<имя T>
продолжительность обучения
T* ptr_;
std::size_t len_;
публичный:
span(T* ptr, std::size_t len) noexcept
: ptr_{ptr}, len_{len}
{}
T& operator[](int i) noexcept {
вернуть *ptr_[i];
}
T const& operator[](int i) const noexcept {
вернуть *ptr_[i];
}
std::size_t size() const noexcept {
верни len_;
}
T* begin() noexcept {{)
вернуть ptr_;
}
T* end() noexcept {{)
вернуть ptr_ + len_;
}
};
Проверьте версию C++11 в реальном времени.
[1]: https://godbolt.org/#g:!((g:!!!) ((h:codeEditor,i:(fontScale:14,j:1,lang:c%2B%2B,selection:(endColumn:2,endLineNumber:16,positionColumn:1,positionLineNumber:1,selectionStartColumn:2,selectionStartLineNumber:16,startColumn:1,startLineNumber:1),source: '%23include+%3Cspan%3E%0A%23include+%3Ciostream%3E%0A%23include+%3Calgorithm%3E%0A%0Aint+main()+%7B%0A++++int+data%5B%5D+%3D+%7B+5,+3,+2,+1,+4+%7D%3B%0A++++std: :span%3Cint%3E+s%7Bdata,+5%7D%3B%0A%0A++++std::sort(s. begin(),+s.end())%3B%0A%0A++++for+(auto+i+:+s)+%7B%0A++++++++std::cout+%3C%3C+i+%3C%3C+%22%5Cn%22%3B%0A++++%7D%0A%0A++++return+0%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:49. 85494633014216,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:! ((h:compiler,i:(compiler:gsnapshot,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'1',trim:'1'),fontScale:14,j:2,lang:c%2B%2B,libs:! (),опции:'-std%3Dc%2B%2B2a',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber: 1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'x86-64+gcc+(trunk)+(Editor+%231,+Compiler+%232)+C%2B%2B',t:'0')),k:50. 145053669857845,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:! ((h:output,i:(компилятор:2,editor:1,fontScale:14,wrap:'1'),l:'5',n:'0',o:'%232+with+x86-64+gcc+(trunk)',t:'0')), header:(),l:'4',m:50,n:'0',o:',s:0,t:'0')),k:50. 145053669857845,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4
El problema es que std::vector
tiene que hacer una copia de los elementos de la matriz con la que la inicializas ya que tiene la propiedad de los objetos que contiene.
Para evitar esto, puedes usar un objeto slice para un array (es decir, similar a lo que std::cadena_vista
es para std::cadena
). Podrías escribir tu propia implementación de plantilla de clase array_view
cuyas instancias se construyen llevando un puntero crudo al primer elemento de un array y a la longitud del array:
#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" no almacena una matriz, sólo mantiene un puntero al principio de la matriz y la longitud de esa matriz. Por lo tanto, los objetos "array_view" son baratos de construir y copiar.
Dado que array_view
proporciona las funciones de miembro begin()
y end()
, puedes usar los algoritmos de biblioteca estándar (por ejemplo, std::sort
, std::find
, std::lower_bound
, etc.) en él:
#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';
}
Salida:
1 2 3 4 5
std::span
(o gsl::span
) en su lugarLa implementación anterior expone el concepto detrás de los objetos rebanados. Sin embargo, desde C++20 puedes usar directamente std::span
en su lugar. En cualquier caso, puedes usar gsl::span
desde C++14.
Поскольку библиотека алгоритмов работает с итераторами, вы можете сохранить массив.
Здесь вы можете использовать необработанные указатели в качестве итераторов. Они поддерживают все операции, которые поддерживает итератор (инкремент, сравнение на равенство, значение of и т.д...):
#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}; // необработанные данные из библиотеки
std::sort(std::begin(data), std::end(data)); // сортировка необработанных данных на месте
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // отображение отсортированных сырых данных
}
}
Но имейте в виду, что это работает, только если data
не разлагается на указатель, потому что тогда информация о длине пропадает.
Вы можете получить итераторы на необработанных массивах и использовать их в алгоритмах:
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); // выделяем буфер на 256 целых чисел
size_t size = get_data_from_library(v.data(), v.size());
// сокращаем буфер до реальных данных. Обратите внимание, что здесь не происходит никаких операций по выделению или копированию памяти.
v.resize(size);
std::sort(v.begin(), v.end());
for (auto i : v) {
cout << i << endl;
}
При использовании C++11 и выше мы можем даже сделать так, чтобы get_data_from_library() возвращала вектор. Благодаря операциям перемещения, копирования памяти не будет.
Теперь я хотел бы использовать std::vector для доступа и изменения этих значений на месте.
Ты не можешь. Это не то, для чего std::vector
есть. std::vector
управляет собственным буфером, который всегда приобретается у аллокатора. Он никогда не берет в собственность другой буфер (кроме как от другого вектора того же типа).
С другой стороны, в этом тоже нет необходимости, потому что ...
Причина в том, что мне нужно применять к этим данным алгоритмы из <алгоритма> (сортировки, подкачки элементов и т.д.).
Эти алгоритмы работают на итераторах. Указатель - это итератор к массиву. Вектор не нужен:
std::sort(data, data + size);
В отличие от шаблонов функций в <алгоритме>
, некоторые инструменты, такие как range-for,std::begin
/std::end
и C++20, работают не только с парой итераторов, но и с контейнерами, такими как векторы. Можно создать класс-обертку для итератора + размер, который будет вести себя как диапазон и работать с этими инструментами. C++20 введет такую обертку в стандартную библиотеку: std::span
.
Вы не можете сделать это с std::vector
без создания копии. std::vector
владеет указателем, который у него под капотом, и выделяет место через аллокатор, который предоставляется.
Если у вас есть доступ к компилятору с поддержкой C++20, вы можете использовать std::span, который был создан именно для этой цели. Он оборачивает указатель и размер в "контейнер", который имеет интерфейс контейнера C++.
Если нет, вы можете использовать gsl::span, на котором основана стандартная версия.
Если вы не хотите импортировать другую библиотеку, вы можете тривиально реализовать это самостоятельно, в зависимости от того, какую функциональность вы хотите иметь.
На самом деле можно почти использовать для этого std::vector
, злоупотребляя функциональностью пользовательского аллокатора, чтобы вернуть указатель на память, которую вы хотите просмотреть. Это не было бы гарантировано стандартом для работы (подкладка, выравнивание, инициализация возвращаемых значений; вам пришлось бы потрудиться при назначении начального размера, а для не-примитивов вам также пришлось бы взламывать свои конструкторы), но на практике я бы ожидал, что это даст достаточно тонких настроек.
Никогда так не делайте. Это уродливо, удивительно, хакки и ненужно. Алгоритмы стандартной библиотеки уже готовы* работать как с сырыми массивами, так и с векторами. Подробности смотрите в других ответах.
Кроме того, другое хорошее предложение о том, что std::span
придет [tag:c++20] и 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");
Особого внимания заслуживает также библиотека диапазона усиления [tag:boost-range], если вас интересует более общая концепция диапазона: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html.
Концепции диапазона также появятся в [tag:c++20].
Вы можете использовать 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'; });
}
Как отмечали другие, std::vector
должен владеть основной памятью (не хватает путаницы с пользовательским аллокатором), поэтому не может быть использован.
Другие также рекомендовали использовать c++20, однако очевидно, что это требует c++20.
Я бы порекомендовал span-lite span. Цитирую субтитры:
span lite - подобный span C++20 для 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
.