Estoy usando una librería externa que en algún momento me da un puntero crudo a un array de enteros y un tamaño.
Ahora me gustaría utilizar std::vector
para acceder y modificar estos valores en su lugar, en lugar de acceder a ellos con punteros en bruto.
Aquí hay un ejemplo articifial que explica el punto:
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
}
Resultado esperado:
1
2
3
4
5
La razón es que necesito aplicar algoritmos de <algoritmo>
(ordenar, intercambiar elementos, etc.) sobre esos datos.
Por otro lado el cambio de tamaño de ese vector nunca se modificaría, por lo que push_back
, erase
, insert
no son necesarios para trabajar en ese vector.
Podría construir un vector basado en los datos de la librería, y utilizarlo para modificar ese vector y copiar los datos de vuelta a la librería, pero eso supondría dos copias completas que me gustaría evitar ya que el conjunto de datos podría ser realmente grande.
std::span
Si eres capaz de usar C++20, podrías usar std::span
que es un puntero - par de longitudes que le da al usuario una vista en una secuencia contigua de elementos. Es una especie de std::cadena_vista
, y mientras que tanto std::span
como std::cadena_vista
son vistas no propietarias, std::cadena_vista
es una vista de sólo lectura.
De los documentos:
La plantilla de clase span describe un objeto que puede referirse a un secuencia de objetos contigua con el primer elemento de la secuencia en la posición cero. Un intervalo puede tener una extensión estática, en la que caso de que el número de elementos de la secuencia se conozca y esté codificado en el tipo, o una extensión dinámica.
Así que lo siguiente funcionaría:
#incluir <span>
#incluye <iostream>
#incluir <algoritmo>
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";
}
...devuelve 0;
}
Compruébalo [en vivo][1]
Ya que std::span
es básicamente un puntero - par de longitudes, también puedes utilizarlo de la siguiente manera:
tamaño_t tamaño = 0;
int *data = get_data_from_library(size);
std::span<int> s{datos, tamaño};
Nota: No todos los compiladores soportan std::span
. Comprueba el soporte del compilador aquí.
ACTUALIZACIÓN
Si no eres capaz de usar C++20, podrías usar gsl::span
que es básicamente la versión base del estándar C++ std::span
.
Si estás limitado al estándar C++11, puedes intentar implementar tu propia y simple clase de "span":
plantilla<nombre de pila T>
la duración de la clase {
T* ptr_;
std::tamaño_t len_;
público:
span(T* ptr, std::size_t len) noexcept
ptr_{ptr}, len_{len}
{}
T& operator[](int i) noexcepto {
devuelve *ptr_[i];
}
T const& operador[](int i) const noexcepto {
devuelve *ptr_[i];
}
std::tamaño_t tamaño() const noexcepto {
Devuelve la len_;
}
T* begin() noexcept {
devuelve ptr_;
}
T* end() noexcepto {
return ptr_ + len_;
}
};
Echa un vistazo a la versión C++11 en vivo
https://godbolt.org/#g:!((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:compilador,i:(compilador:gsnapshot,filtros:(b:'0',binario:'1',commentOnly:'0',demangle:'0',directivas:'0',ejecutar:'0',intel:'0',libraryCode:'1',trim:'1'),fontScale:14,j:2,lang:c%2B%2B,libs:! (),opciones:'-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,+Compilador+%232)+C%2B%2B',t:'0'),k:50. 145053669857845,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:! ((h:salida,i:(compilador:2,editor:1,fuenteEscala:14,envoltura:'1'),l:'5',n:'0',o:'%232+con+x86-64+gcc+(tronco)',t:'0')),encabezamiento:(),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.
Como la biblioteca de algoritmos trabaja con iteradores, puedes mantener el array.
Aquí puedes usar punteros crudos como iteradores. Soportan todas las operaciones que soporta un iterador (incremento, comparación por igualdad, valor de, etc...):
#include <iostream>
#include <algoritmo>
int *get_data_from_library(int &tamaño) {
static int datos[] = {5,3,2,1,4};
tamaño = 5;
devuelve los datos;
}
int main()
{
int tamaño;
int *data = get_data_from_library(size);
std::sort(datos, datos + tamaño);
for (int i = 0; i < tamaño; i++)
{
std::cout << data[i] << "|n";
}
}
data
apunta al primer miembro del array como un iterador devuelto por begin()
y data + size
apunta al elemento después del último elemento del array como un iterador devuelto por end()
.
Aquí puedes usar std::begin()
y std::end()
.
#include <iostream>;
#include <algoritmo>
int main()
{
int data[] = {5,3,2,1,4}; // datos brutos de la biblioteca
std::sort(std::begin(data), std::end(data)); // ordenar los datos brutos en su lugar
for (int i = 0; i < 5; i++)
{
std::cout << data[i] << "\n"; // mostrar los datos brutos ordenados
}
}
Pero ten en cuenta que esto sólo funciona, si data
no decae a un puntero, porque entonces la información de la longitud se pierde.
Puedes obtener iteradores en arrays sin procesar y utilizarlos en algoritmos:
int datos[] = {5,3,2,1,4};
std::sort(std::begin(data), std::end(data));
for (auto i : datos) {
std::cout << i << std::endl;
}
Si se trabaja con punteros en bruto (ptr + size), se puede utilizar la siguiente técnica
size_t tamaño = 0;
int * data = get_data_from_library(size);
auto b = datos;
auto e = b + tamaño;
std::sort(b, e);
for (auto it = b; it != e; ++it) {
cout << *it << endl;
}
UPD: Sin embargo, el ejemplo anterior está mal diseñado. La librería nos devuelve un puntero sin procesar y no sabemos dónde está asignado el buffer subyacente y quién debe liberarlo.
Normalmente, el llamante proporciona un buffer para que la función llene los datos. En ese caso, podemos preasignar el vector y utilizar su buffer subyacente:
std::vector<int> v;
v.resize(256); // asignar un buffer para 256 enteros
size_t size = get_data_from_library(v.data(), v.size());
// reducir los datos reales. Nótese que aquí no se realiza ninguna reasignación de memoria ni copia.
v.resize(size);
std::sort(v.begin(), v.end());
for (auto i : v) {
cout << i << endl;
}
Si usamos C++11 o superior podemos incluso hacer que get_data_from_library() devuelva un vector. Gracias a las operaciones de movimiento, no habrá copia de memoria.
Ahora me gustaría usar std::vector para acceder y modificar estos valores en su lugar
No puedes. No es para eso que "STD::vector" es para. "STD::vector" maneja su propio buffer, que siempre se adquiere de un asignador. Nunca toma la propiedad de otro buffer (excepto de otro vector del mismo tipo).
Por otro lado, tampoco lo necesita porque ...
La razón es que necesito aplicar algoritmos de
(clasificación, intercambio de elementos, etc.) en esos datos.
Esos algoritmos funcionan con iteradores. Un puntero es un iterador de una matriz. No necesitas un vector:
std::sort(data, data + size);
A diferencia de las plantillas de funciones en <algoritmo>
, algunas herramientas como range-for,std::begin
/std::end
y C++20 ranges no funcionan con sólo un par de iteradores, aunque sí con contenedores como los vectores. Es posible crear una clase de envoltura para el iterador + tamaño que se comporte como un rango, y funcione con estas herramientas. C++20 introducirá tal envoltura en la biblioteca estándar: std::span
.
No puedes hacer esto con un std::vector
sin hacer una copia. std::vector
posee el puntero que tiene bajo el capó y asigna el espacio a través del asignador que se proporciona.
Si tienes acceso a un compilador que tenga soporte para C++20 puedes usar std::span que fue construido exactamente para este propósito. Envuelve un puntero y un tamaño en un "contenedor" que tiene la interfaz de contenedores de C++.
Si no, puedes usar gsl::span que es en lo que se basó la versión estándar.
Si no quieres importar otra librería, puedes implementar esto tú mismo de forma trivial dependiendo de la funcionalidad que quieras tener.
De hecho, casi podrías usar "STD::vector" para esto, abusando de la funcionalidad del asignador personalizado para devolver un puntero a la memoria que quieres ver. Eso no estaría garantizado por el estándar de trabajo (relleno, alineación, inicialización de los valores devueltos; tendrías que esforzarte en asignar el tamaño inicial, y para los no primitivos también tendrías que hackear tus constructores), pero en la práctica esperaría que diera suficientes retoques.
Nunca jamás hagas eso. Es feo, sorprendente, hacky, e innecesario. Los algoritmos de la librería estándar están diseñados para trabajar tanto con matrices crudas como con vectores. Vea las otras respuestas para más detalles sobre eso.
Además de la otra buena sugerencia sobre std::span
que viene en [tag:c++20] y gsl:span
, incluyendo tu propia (ligera) clase de span
hasta entonces ya es bastante fácil (siéntete libre de copiar):
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");
También es de especial importancia la biblioteca de rango de impulso [tag:boost-range] si está interesado en el concepto de rango más genérico: https://www.boost.org/doc/libs/1_60_0/libs/range/doc/html/range/reference/utilities/iterator_range.html.
Los conceptos de rango también llegarán en [tag:c++20]
Podrías usar un std::reference_wrapper
disponible desde 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'; });
}
Como otros han señalado, std::vector
debe ser dueño de la memoria subyacente (sin necesidad de jugar con un asignador personalizado) por lo que no puede ser utilizado.
Otros también han recomendado el span de c++20, sin embargo obviamente eso requiere c++20.
Yo recomendaría el span de span-lite. Para citarlo, es un subtítulo:
span lite - Un span similar a C++20 para C++98, C++11 y más tarde en una biblioteca de un solo archivo de cabecera
Proporciona una visión no propietaria y mutable (como en que se pueden mutar los elementos y su orden pero no insertarlos) y como dice la cita no tiene dependencias y funciona en la mayoría de los compiladores.
Su ejemplo:
#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";
}
}
Impresiones
1
2
3
4
5
Esto también tiene la ventaja de que si un día cambias a c++20, deberías ser capaz de reemplazar este "no-std::span" por "std::span".