Última actualización:2026-10-12
Diseño y trampas de std::span en C++20¶
0.Motivación¶
En el código C/C++ tradicional, solemos usar un puntero + un tamaño para acceder a un array, por ejemplo:
span simplifica esta operación, que queda así:
Entonces, ¿qué es exactamente un span, cuáles son sus escenarios de uso y cuáles sus trampas? Esta sección lo explora en detalle.
1. ¿Qué es un span?¶
En C++20, span es un contenedor ligero de tipo rango que proporciona acceso a un tramo de memoria contigua. Como vista (view), span no posee sus datos, sino que es una simple referencia a ellos, y se usa habitualmente en sustitución de los arrays o punteros tradicionales de C. Puede alojar arrays de cualquier tipo y contenedores estándar, como std::vector, y ofrece una forma más segura y moderna de procesar estos datos.
Varios puntos clave:
- Memoria contigua
- No posee los datos
- Referencia
El código fuente de span se encuentra en gcc, en libstdc++-v3/include/std/span.
El tipo span incluye dos parámetros importantes:
_Type: el tipo de los elementos.Extent: el tamaño del array. Si esdynamic_extent(el valor por defecto), significa que el tamaño de la vista puede variar dinámicamente.
2. El span dinámico y el estático¶
span tiene dos modos: estático y dinámico.
2.1 span estático¶
El span estático determina su tamaño en tiempo de compilación; su Extent es un valor constante. Al usar un span estático, necesitamos conocer el tamaño de los datos en tiempo de compilación.
Por ejemplo:
El tamaño del span estático queda fijado en tiempo de compilación, por lo que el compilador puede aprovecharlo para algunas optimizaciones, como omitir la comprobación de límites. Pero como Extent es constante, el span estático no puede modificar su tamaño en tiempo de ejecución.
2.2 span dinámico¶
El tamaño del span dinámico se determina en tiempo de ejecución; Extent es por defecto dynamic_extent. Este tipo de span no exige conocer el tamaño en tiempo de compilación y puede adaptarse a arrays o contenedores de cualquier tamaño.
Por ejemplo:
El span dinámico es adecuado para los casos en que el tamaño no puede determinarse en tiempo de compilación, y ofrece mayor flexibilidad.
Su implementación en el código fuente de gcc es bastante sencilla: por defecto es un entero enorme (el valor sin signo de -1), y el cálculo real se hace en la construcción.
Por ejemplo: aquí se usa ranges::size para fijar el tamaño interno; consulta el código siguiente.
Establece el tamaño dinámico.
3. subspan¶
span proporciona una función miembro muy útil, subspan, que permite extraer una subvista de un span existente sin copiar datos. Resulta muy adecuado al procesar conjuntos de datos grandes, evitando costes de memoria innecesarios.
El método subspan() tiene dos sobrecargas:
- Una toma
__offsety__countcomo parámetros, donde__offsetes la posición inicial y__countel tamaño del subrango (por defectodynamic_extent, es decir, hasta el final delspanoriginal). - La otra no tiene parámetros y se implementa mediante plantillas.
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
template<size_t _Offset, size_t _Count = dynamic_extent>
[[nodiscard]]
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
Ejemplo 1:
Ejemplo 2:
4. Trampas de uso¶
Aunque span es una herramienta muy útil, al usarlo hay varias trampas habituales que conviene tener en cuenta:
4.1 No dejes que un span referencie memoria ya liberada¶
El propio span no posee los datos; es solo una referencia a ellos. Por tanto, si la memoria referenciada por el span se libera (por ejemplo, el array original se destruye o sale del ámbito), acceder a ese span provocará comportamiento indefinido.
Por ejemplo:
std::span<int> create_span() {
int arr[5] = {1, 2, 3, 4, 5};
return std::span<int>(arr, 5);
}
// 错误:返回的 span 引用了栈上临时变量,函数返回后 arr 被销毁
auto s = create_span();
Para evitar este problema, asegúrate de que los datos referenciados por el span siguen siendo válidos durante todo su ciclo de vida. Puede considerarse mantener los datos en memoria asignada dinámicamente, o garantizar que el ciclo de vida de los datos coincida con el del span.
4.2 Problemas al cambiar el tamaño del array subyacente¶
La referencia de span a los datos subyacentes es directa, no una copia. Cuando los datos subyacentes cambian, el propio span no se entera. Por eso, al cambiar el tamaño del array o contenedor subyacente (por ejemplo, reasignando memoria), el span original puede quedar invalidado.
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> s(vec); // s 引用 vec 的数据
// 改变底层容器的大小
vec.push_back(6); // vec 大小变为 6
// 这里 s 仍然引用 vec 的数据,但 vec 可能已经重新分配了内存
// 这可能导致 s 引用失效或未定义行为
Temas relacionados¶
- Introducción a las corrutinas de C++20 | C++那些事
- C++20 channel en la práctica | C++那些事
- Conceptos Concepts de C++20 | C++那些事
- constexpr y la expresión coma en C++20 | C++那些事
- Orden de bytes endian en C++20 | C++那些事
- Expresiones lambda de C++20 (parte 1) | C++那些事
- Módulos Modules de C++20 | C++那些事
- La biblioteca Ranges de C++20 | C++那些事
- Guía de las novedades de C++20 | C++那些事
- C++20 [[likely]] y [[unlikely]] | C++那些事