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.

Comentarios sobre la pregunta (11)

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 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 
#incluir 

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

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

    for (auto const i : s) {
        std::cout 
Comentarios (2)
Solución

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 

template
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 av(arr, LEN);

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

   for (auto const& val: av)
      std::cout 
Comentarios (2)

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...):


#include 
#include 

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 
Comentarios (1)

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 
Comentarios (4)

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 `, 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`.

Comentarios (0)

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.

Comentarios (0)

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.

Comentarios (3)

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
struct span {
    T* first;
    size_t length;
    span(T* first_, size_t length_) : first(first_), length(length_) {};
    using value_type = std::remove_cv_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 
Comentarios (6)

Podrías usar un [std::reference_wrapper][1] disponible desde C++11:


#include 
#include 
#include 
#include 

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 
Comentarios (10)

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:

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 
#include 
#include 

#include 

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 v{get_data_from_library(), size};

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

  for (auto i = 0UL; i < v.size(); ++i)
  {
    std::cout 
Comentarios (0)