Uso de std::vector como vista en la memoria bruta
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.
C++20's
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 destd::cadena_vista
, y mientras que tantostd::span
comostd::cadena_vista
son vistas no propietarias,std::cadena_vista
es una vista de sólo lectura.De los documentos:
Así que lo siguiente funcionaría:
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 parastd::cadena
). Podrías escribir tu propia implementación de plantilla de clasearray_view
cuyas instancias se construyen llevando un puntero crudo al primer elemento de un array y a la longitud del array:"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 miembrobegin()
yend()
, puedes usar los algoritmos de biblioteca estándar (por ejemplo,std::sort
,std::find
,std::lower_bound
, etc.) en él:Como la biblioteca de algoritmos trabaja con iteradores, puedes mantener el array.
Para punteros y longitud de array conocida
Aquí puedes usar punteros crudos como iteradores. Soportan todas las operaciones que soporta un iterador (incremento, comparación por igualdad, valor de, etc...):
Puedes obtener iteradores en arrays sin procesar y utilizarlos en algoritmos:
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 ...
Esos algoritmos funcionan con iteradores. Un puntero es un iterador de una matriz. No necesitas un vector:
A diferencia de las plantillas de funciones en `
, algunas herramientas como range-for,
std::begin/
std::endy 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] ygsl:span
, incluyendo tu propia (ligera) clase despan
hasta entonces ya es bastante fácil (siéntete libre de copiar):Podrías usar un [
std::reference_wrapper
][1] disponible desde C++11: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][1]. Para citarlo, es un subtítulo:
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: