¿Qué es un "lapso" y cuándo debo usar uno?

237

Recientemente recibí sugerencias para usar span<T>'s en mi código, o he visto algunas respuestas aquí en el sitio que usan span' supuestamente algún tipo de contenedor. Pero no puedo encontrar algo así en la biblioteca estándar de C ++ 17.

Entonces, ¿qué es esto misterioso span<T>y por qué (o cuándo) es una buena idea usarlo si no es estándar?

einpoklum
fuente
std::spanse propuso en 2017. Se aplica a C ++ 17 o C ++ 20. Consulte también P0122R5, span: vistas de límites seguros para secuencias de objetos . ¿De verdad quieres apuntar a ese idioma? Pasarán años antes de que los compiladores se pongan al día.
jww
66
@jww: los span son bastante utilizables con C ++ 11 ... en gsl::spanlugar de hacerlo std::span. Vea también mi respuesta a continuación.
einpoklum
También documentado en cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: No en 2017 no fue ...
einpoklum
@jww Todos los compiladores admiten std :: span <> ahora en modo C ++ 20. Y span está disponible en muchas librerías de terceros. Tenías razón: fueron años: 2 años para ser precisos.
Contango

Respuestas:

272

¿Qué es?

A span<T>es:

  • Una abstracción muy ligera de una secuencia contigua de valores de tipo Ten algún lugar de la memoria.
  • Básicamente, struct { T * ptr; std::size_t length; }con un montón de métodos convenientes.
  • Un tipo no propietario (es decir, un "tipo de referencia" en lugar de un "tipo de valor"): nunca asigna ni desasigna nada y no mantiene vivos los punteros inteligentes.

Anteriormente se conocía como array_view e incluso antes como array_ref.

¿Cuándo debería usarlo?

Primero, cuando no usarlo:

  • No lo use en un código que pueda tomar cualquier par de iteradores de inicio y fin, como std::sort,std::find_if , std::copyy todas esas funciones con plantilla súper-genérica.
  • No lo use si tiene un contenedor de biblioteca estándar (o un contenedor Boost, etc.) que sabe que es el adecuado para su código. No está destinado a suplantar a ninguno de ellos.

Ahora para saber cuándo usarlo realmente:

Use span<T>(respectivamente, span<const T>) en lugar de un dispositivo independiente T*(respectivamenteconst T* ) para el que tiene el valor de longitud. Entonces, reemplace funciones como:

  void read_into(int* buffer, size_t buffer_size);

con:

  void read_into(span<int> buffer);

¿Por qué debería usarlo? ¿Por qué es algo bueno?

¡Oh, los tramos son increíbles! Usando unspan ...

  • significa que puede trabajar con esa combinación de puntero + longitud / inicio + fin como lo haría con un contenedor de biblioteca estándar sofisticado, p. ej.:

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.begin(), my_span.end(), some_predicate);

    ... pero sin ninguno de los gastos generales en que incurre la mayoría de las clases de contenedores.

  • permite que el compilador trabaje más por usted a veces. Por ejemplo, esto:

    int buffer[BUFFER_SIZE];
    read_into(buffer, BUFFER_SIZE);

    se convierte en esto:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... que hará lo que quieras que haga. Véase también la directriz P.5 .

  • es la alternativa razonable a pasar const vector<T>& a funciones cuando espera que sus datos sean contiguos en la memoria. ¡No más ser regañado por los grandes y poderosos gurús de C ++!

  • facilita el análisis estático, por lo que el compilador podría ayudarlo a detectar errores tontos.
  • permite la instrumentación de compilación de depuración para la verificación de límites de tiempo de ejecución (es decir span, los métodos tendrán algún código de verificación de límites dentro de #ifndef NDEBUG... #endif)
  • indica que su código (que usa el span) no posee la memoria apuntada.

Hay aún más motivación para usar spans, que puedes encontrar en las pautas básicas de C ++ , pero captas la deriva.

¿Por qué no está en la biblioteca estándar (a partir de C ++ 17)?

Está en la biblioteca estándar, pero solo a partir de C ++ 20. La razón es que todavía es bastante nueva en su forma actual, concebida en conjunto con el proyecto de directrices centrales de C ++ , que solo ha estado tomando forma desde 2015. (Aunque como señalan los comentaristas, tiene una historia anterior).

Entonces, ¿cómo lo uso si aún no está en la biblioteca estándar?

Es parte de la Biblioteca de soporte de las Pautas principales (GSL). Implementaciones:

  • El GSL de Microsoft / Neil Macintosh contiene una implementación independiente:gsl/span
  • GSL-Lite es una implementación de un solo encabezado de todo el GSL (no es tan grande, no se preocupe), incluido span<T>.

La implementación de GSL generalmente asume una plataforma que implementa soporte C ++ 14 [ 14 ]. Estas implementaciones alternativas de encabezado único no dependen de las instalaciones de GSL:

Tenga en cuenta que estas implementaciones de span diferentes tienen algunas diferencias en los métodos / funciones de soporte que vienen con; y también pueden diferir algo de la versión que entra en la biblioteca estándar en C ++ 20.


Lecturas adicionales: puede encontrar todos los detalles y consideraciones de diseño en la propuesta oficial final antes de C ++ 17, P0122R7: span: vistas seguras para las secuencias de objetos de Neal Macintosh y Stephan J. Lavavej. Aunque es un poco largo. Además, en C ++ 20, la semántica de comparación de tramo cambió (siguiendo este breve artículo de Tony van Eerd).

einpoklum
fuente
2
Tendría más sentido estandarizar un rango general (compatible con iterador + centinela e iterador + longitud, tal vez incluso iterador + centinela + longitud) y hacer que abarque un simple typedef. Porque, ya sabes, eso es más genérico.
Deduplicador
3
@Deduplicator: los rangos están llegando a C ++, pero la propuesta actual (por Eric Niebler) requiere soporte para Concepts. Entonces no antes de C ++ 20.
einpoklum
8
@ HảiPhạmLê: Las matrices no se descomponen inmediatamente en punteros. intente hacerlo std::cout << sizeof(buffer) << '\n'y verá que obtiene 100 sizeof (int) 's.
einpoklum
44
@ Jim std::arrayes un contenedor, posee los valores. spanno es propietario
Caleth
3
@ Jim: std::arrayes una bestia completamente diferente. Su longitud se fija en tiempo de compilación y es un tipo de valor en lugar de un tipo de referencia, como explicó Caleth.
einpoklum
1

@einpoklum hace un muy buen trabajo al presentar lo que spanhay en su respuesta aquí . Sin embargo, incluso después de leer su respuesta, es fácil para alguien nuevo tener una secuencia de preguntas de flujo de pensamiento que no se responden completamente, como las siguientes:

  1. Como es un span diferente de una matriz C? ¿Por qué no solo usar uno de esos? Parece que es solo uno de esos con el tamaño conocido también ...
  2. Espera, eso suena como un std::array, ¿cómo es spandiferente de eso?
  3. Oh, eso me recuerda, ¿no es std::vectorcomo un std::arraytambién?
  4. Estoy tan confundida. :( ¿Qué es un span?

Entonces, aquí hay alguna claridad adicional sobre eso:

CITA DIRECTA DE SU RESPUESTA - CON MIS ADICIONES EN NEGRITA :

¿Qué es?

A span<T>es:

  • Una abstracción muy ligera de una secuencia contigua de valores de tipo Ten algún lugar de la memoria.
  • Básicamente, una estructura única{ T * ptr; std::size_t length; } con un montón de métodos convenientes. (Tenga en cuenta que esto es claramente diferente de std::array<>porque spanhabilita métodos de acceso convenientes, comparables a std::array, a través de un puntero, para escribirT y longitud (número de elementos) de tipo T, mientras que std::arrayes un contenedor real que contiene uno o más valores de tipo T).
  • Un tipo no propietario (es decir, un "tipo de referencia" en lugar de un "tipo de valor"): nunca asigna ni desasigna nada y no mantiene vivos los punteros inteligentes.

Anteriormente se conocía comoarray_view e incluso antes como array_ref.

Esas partes audaces son críticas para la comprensión de uno, ¡así que no las extrañe ni las lea mal! A spanNO es una matriz C de estructuras, ni es una estructura de una matriz C de tipo Tmás la longitud de la matriz (esto sería esencialmente lo std::array que es el contenedor ), NOR es una matriz C de estructuras de punteros escribir Tmás la longitud, sino que es una estructura única que contiene un solo puntero para escribirT , y la longitud , que es el número de elementos (de tipo T) en el bloque de memoria contigua al que Tapunta el puntero . De esta manera, la única sobrecarga que ha agregado utilizando unspanson las variables para almacenar el puntero y la longitud, y cualquier función de acceso conveniente que utilice quespan proporciona.

Esto es UNLIKE a std::array<>porque std::array<>realmente asigna memoria para todo el bloque contiguo, y es UNLIKE std::vector<>porque std::vectorbásicamente es solo un std::arrayque también hace un crecimiento dinámico (generalmente duplicando su tamaño) cada vez que se llena e intenta agregarle algo más. . A std::arraytiene un tamaño fijo, y a spanni siquiera administra la memoria del bloque al que apunta, solo apunta al bloque de memoria, sabe cuánto dura el bloque de memoria, sabe qué tipo de datos hay en una matriz C. en la memoria, y proporciona funciones de acceso convenientes para trabajar con los elementos en esa memoria contigua .

Se es parte de la C ++ estándar:

std::spanes parte del estándar C ++ a partir de C ++ 20. Puede leer su documentación aquí: https://en.cppreference.com/w/cpp/container/span . Para ver cómo usar Google absl::Span<T>(array, length)en C ++ 11 o posterior hoy , vea a continuación.

Descripciones resumidas y referencias clave:

  1. std::span<T, Extent>( Extent= "el número de elementos en la secuencia, o std::dynamic_extentsi es dinámico". Un intervalo solo apunta a la memoria y facilita el acceso, ¡pero NO lo gestiona!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(¡tenga en cuenta que tiene un tamaño fijoN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (automáticamente aumenta de tamaño dinámicamente según sea necesario):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

¿Cómo puedo usar spanen C ++ 11 o posterior hoy ?

Google ha abierto sus bibliotecas internas de C ++ 11 en forma de su biblioteca "Abseil". Esta biblioteca está diseñada para proporcionar características de C ++ 14 a C ++ 20 y posteriores que funcionan en C ++ 11 y versiones posteriores, para que pueda usar las características del mañana, hoy. Ellos dicen:

Compatibilidad con el estándar C ++

Google ha desarrollado muchas abstracciones que coinciden o coinciden estrechamente con las características incorporadas en C ++ 14, C ++ 17 y más allá. El uso de las versiones Abseil de estas abstracciones le permite acceder a estas funciones ahora, incluso si su código aún no está listo para la vida en un mundo posterior a C ++ 11.

Aquí hay algunos recursos y enlaces clave:

  1. Sitio principal: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Repositorio de GitHub: https://github.com/abseil/abseil-cpp
  4. span.hencabezado y absl::Span<T>(array, length)clase de plantilla: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
fuente
1
Creo que traes información importante y útil, ¡gracias!
Gui Lima