Última atualização:2026-10-12
Design e armadilhas do std::span no C++20¶
0.Motivação¶
No código C/C++ tradicional, geralmente usamos um ponteiro + tamanho para acessar um array, por exemplo:
O span pode simplificar essa operação, tornando-a:
Então, o que é exatamente um span, quais são seus cenários de uso e quais são suas armadilhas? Esta seção explora isso em detalhes.
1. O que é span?¶
No C++20, span é um contêiner leve, do tipo range, que fornece acesso a um trecho de memória contígua. Como view (visão), o span não possui seus dados; é apenas uma referência simples aos dados, usado com frequência como substituto dos arrays estilo C ou ponteiros tradicionais. Ele pode acomodar arrays e contêineres padrão de qualquer tipo, como std::vector, oferecendo uma forma mais segura e moderna de tratar esses dados.
Alguns pontos-chave:
- Memória contígua
- Não possui os dados
- Referência
O código-fonte de span está em gcc, em libstdc++-v3/include/std/span.
O tipo span contém dois parâmetros importantes:
_Type: o tipo do elemento.Extent: o tamanho do array. Se fordynamic_extent(o valor padrão), significa que o tamanho da view pode variar dinamicamente.
2. span dinâmico e estático¶
O span tem dois modos: estático e dinâmico.
2.1 span estático¶
O span estático determina seu tamanho em tempo de compilação; seu Extent é um valor constante. Ao usar um span estático, precisamos conhecer o tamanho dos dados em tempo de compilação.
Por exemplo:
O tamanho do span estático é fixado em tempo de compilação; portanto, é possível aproveitar o compilador para algumas otimizações, como a omissão de verificação de limites. Como Extent é uma constante, o span estático não pode ter o tamanho modificado em tempo de execução.
2.2 span dinâmico¶
O tamanho do span dinâmico é determinado em tempo de execução; Extent assume por padrão dynamic_extent. Esse tipo de span não exige que o tamanho seja conhecido em tempo de compilação e pode se adaptar a arrays ou contêineres de qualquer tamanho.
Por exemplo:
O span dinâmico é adequado para situações em que o tamanho não pode ser determinado em tempo de compilação, oferecendo maior flexibilidade.
Na implementação do código-fonte do gcc, isso é bem simples: por padrão é um inteiro muito grande (o unsigned de -1); o cálculo real acontece na construção.
Por exemplo: aqui usa-se ranges::size para definir o tamanho interno; veja o código abaixo.
Define o tamanho dinamicamente.
3. subspan¶
O span fornece uma função-membro muito útil, subspan, que nos permite extrair uma subvisão de um span existente sem copiar dados. Ela é muito adequada para evitar overhead de memória desnecessário ao tratar grandes volumes de dados.
O método subspan() tem duas sobrecargas:
- Uma aceita
__offsete__countcomo parâmetros, em que__offseté a posição inicial e__counté o tamanho do subintervalo (por padrão,dynamic_extent, ou seja, até o fim dospanoriginal). - Outra não tem parâmetros e é implementada com base em templates.
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>()> {}
Exemplo 1:
Exemplo 2:
4. Armadilhas de uso¶
Embora o span seja uma ferramenta muito útil, há algumas armadilhas comuns a observar no uso:
4.1 Não deixe o span referenciar memória já liberada¶
O span em si não possui os dados; é apenas uma referência a eles. Portanto, se a memória referenciada pelo span for liberada (por exemplo, o array original é destruído ou sai do escopo), acessar esse span resultará em comportamento indefinido.
Por exemplo:
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 resolver esse problema, garanta que os dados referenciados pelo span sejam válidos durante todo o seu tempo de vida. Considere manter os dados em memória alocada dinamicamente, ou assegurar que o tempo de vida dos dados coincida com o do span.
4.2 Problemas ao alterar o tamanho do array subjacente¶
A referência do span aos dados subjacentes é direta, e não uma cópia. Quando os dados subjacentes mudam, o span em si não percebe essas mudanças. Assim, ao alterar o tamanho do array ou contêiner subjacente (por exemplo, por meio de realocação de memória), o span original pode se tornar inválido.
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 引用失效或未定义行为
Tópicos relacionados¶
- Introdução às corrotinas no C++20 | C++那些事
- Prática com channel no C++20 | C++那些事
- Concepts (conceitos) no C++20 | C++那些事
- constexpr e a expressão vírgula no C++20 | C++那些事
- Ordem de bytes (endian) no C++20 | C++那些事
- Expressões Lambda no C++20 (parte 1) | C++那些事
- Módulos (Modules) no C++20 | C++那些事
- Biblioteca Ranges do C++20 | C++那些事
- Guia dos novos recursos do C++20 | C++那些事
- [[likely]] e [[unlikely]] no C++20 | C++那些事