O que é uma extensão e quando devo usar uma?

237

Recentemente, recebi sugestões de uso span<T>no meu código ou vi algumas respostas aqui no site que usam spansupostamente algum tipo de contêiner. Mas - não consigo encontrar nada parecido na biblioteca padrão do C ++ 17.

Então, o que é misterioso span<T>e por que (ou quando) é uma boa ideia usá-lo se não é padrão?

einpoklum
fonte
std::spanfoi proposto em 2017. Aplica-se ao C ++ 17 ou C ++ 20. Consulte também P0122R5, visualizações span: safe-safe para seqüências de objetos . Deseja realmente segmentar esse idioma? Levará anos até que os compiladores os recuperem.
JWW
6
@jww: span são bastante utilizáveis ​​com C ++ 11 ... gsl::spane não std::span. Veja também minha resposta abaixo.
einpoklum
Também documentado em cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: Não em 2017, não era ...
einpoklum 24/03
@jww Todos os compiladores suportam std :: span <> agora no modo C ++ 20. E o span está disponível em muitas bibliotecas de terceiros. Você estava certo - foram anos: 2 anos para ser mais preciso.
Contango 20/06

Respostas:

272

O que é isso?

A span<T>é:

  • Uma abstração muito leve de uma sequência contígua de valores do tipo Tem algum lugar da memória.
  • Basicamente, struct { T * ptr; std::size_t length; }com um monte de métodos de conveniência.
  • Um tipo não proprietário (ou seja, um "tipo de referência" em vez de um "tipo de valor"): nunca aloca nem desaloca nada e não mantém ativos os indicadores inteligentes.

Era conhecido anteriormente como array_viewe até mais cedo como array_ref.

Quando devo usá-lo?

Primeiro, quando não usá-lo:

  • Não usá-lo no código que só poderia tomar qualquer par de Start & finais iterators, como std::sort, std::find_if, std::copye todas essas funções de super-generic templated.
  • Não o use se você tiver um contêiner de biblioteca padrão (ou um contêiner Boost etc.) que você sabe que é o ajuste certo para o seu código. Não se destina a suplantar nenhum deles.

Agora, quando realmente usá-lo:

Use span<T>(respectivamente span<const T>) em vez de um autônomo T*(respectivamente const T*) para o qual você tenha o valor do comprimento. Portanto, substitua funções como:

  void read_into(int* buffer, size_t buffer_size);

com:

  void read_into(span<int> buffer);

Por que devo usá-lo? Por que isso é uma coisa boa?

Oh, vãos são incríveis! Usando um span...

  • significa que você pode trabalhar com essa combinação de ponteiro + comprimento / início + fim, como faria com um contêiner de biblioteca padrão sofisticado e sofisticado, por exemplo:

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

    ... mas com absolutamente nenhuma das despesas gerais incorridas pela maioria das classes de contêineres.

  • permite que o compilador faça mais trabalho para você às vezes. Por exemplo, isto:

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

    torna-se este:

    int buffer[BUFFER_SIZE];
    read_into(buffer);

    ... o que fará o que você gostaria que ele fizesse. Veja também a Diretriz P.5 .

  • é a alternativa razoável para passar const vector<T>&para funções quando você espera que seus dados sejam contíguos na memória. Chega de ser repreendido por grandes e poderosos gurus de C ++!

  • facilita a análise estática; portanto, o compilador poderá ajudá-lo a detectar erros tolos.
  • permite instrumentação de compilação de depuração para verificação de limites de tempo de execução (ou seja span, os métodos terão algum código de verificação de limites dentro de #ifndef NDEBUG... #endif)
  • indica que seu código (que está usando a extensão) não possui a memória apontada.

Há ainda mais motivação para usar spans, que você pode encontrar nas diretrizes principais do C ++ - mas você percebe a diferença.

Por que não está na biblioteca padrão (a partir do C ++ 17)?

Está na biblioteca padrão - mas apenas a partir do C ++ 20. O motivo é que ele ainda é bastante novo em sua forma atual, concebido em conjunto com o projeto de diretrizes principais do C ++ , que só vem se formando desde 2015. (Embora, como comentam os comentaristas, ele tenha um histórico anterior).

Então, como eu o uso se ainda não estiver na biblioteca padrão?

Faz parte da Biblioteca de suporte das diretrizes principais (GSL). Implementações:

  • O GSL da Microsoft / Neil Macintosh contém uma implementação autônoma:gsl/span
  • O GSL-Lite é uma implementação de cabeçalho único de todo o GSL (não é tão grande, não se preocupe), inclusive span<T>.

A implementação da GSL geralmente assume uma plataforma que implementa o suporte ao C ++ 14 [ 14 ]. Essas implementações alternativas de cabeçalho único não dependem dos recursos da GSL:

Observe que essas implementações de span diferentes têm algumas diferenças em quais métodos / funções de suporte eles vêm; e eles também podem diferir um pouco da versão que entra na biblioteca padrão em C ++ 20.


Leitura adicional: Você pode encontrar todos os detalhes e considerações de design na proposta oficial final antes de C ++ 17, P0122R7: span: visualizações seguras de limites para seqüências de objetos por Neal Macintosh e Stephan J. Lavavej. É um pouco longo, no entanto. Além disso, no C ++ 20, a semântica da comparação de span foi alterada (seguindo este pequeno artigo de Tony van Eerd).

einpoklum
fonte
2
Faria mais sentido padronizar um intervalo geral (suportando iterador + sentinela e iterador + comprimento, talvez até iterador + sentinela + comprimento) e tornar o span um simples typedef. Porque, você sabe, isso é mais genérico.
Deduplicator
3
@ Reduplicador: as faixas estão chegando ao C ++, mas a proposta atual (de Eric Niebler) requer suporte para Conceitos. Portanto, não antes do C ++ 20.
Einpoklum
8
@ HảiPhạmLê: Arrays não se deterioram imediatamente em ponteiros. tente fazer std::cout << sizeof(buffer) << '\n'e você verá 100 sizeof (int) 's.
einpoklum
4
@ Jim std::arrayé um contêiner, possui os valores. spannão é proprietário
Caleth
3
@ Jim: std::arrayé um animal completamente diferente. Seu comprimento é fixo no tempo de compilação e é um tipo de valor em vez de um tipo de referência, como Caleth explicou.
einpoklum
1

@einpoklum faz um bom trabalho ao apresentar o que spané em sua resposta aqui . No entanto, mesmo depois de ler sua resposta, é fácil para alguém novo se estender ainda ter uma sequência de perguntas que não são totalmente respondidas, como as seguintes:

  1. Qual é a spandiferença de uma matriz C? Por que não usar apenas um desses? Parece que é apenas um daqueles com o tamanho conhecido também ...
  2. Espere, isso soa como um std::array, como é spandiferente disso?
  3. Oh, isso me lembra, não é std::vectorcomo um std::arraytambém?
  4. Estou tão confuso. :( O que é um span?

Então, aqui está uma clareza adicional sobre isso:

CITAÇÃO DIRETA DE SUA RESPOSTA - COM MINHAS ADIÇÕES EM NEGRITO :

O que é isso?

A span<T>é:

  • Uma abstração muito leve de uma sequência contígua de valores do tipo Tem algum lugar da memória.
  • Basicamente, uma estrutura única{ T * ptr; std::size_t length; } com vários métodos de conveniência. (Observe que isso é distintamente diferente de std::array<>porque um spanpossibilita métodos acessadores de conveniência, comparáveis ​​a std::array, por meio de um ponteiro, ao tipoT e comprimento (número de elementos) do tipo T, enquanto std::arrayé um contêiner real que contém um ou mais valores do tipo T.
  • Um tipo não proprietário (ou seja, um "tipo de referência" em vez de um "tipo de valor"): nunca aloca nem desaloca nada e não mantém ativos os indicadores inteligentes.

Era conhecido anteriormente como array_viewe até mais cedo como array_ref.

Essas partes ousadas são críticas para o entendimento, portanto, não as perca ou as interprete mal! A spanNÃO é uma matriz C de estruturas, nem é uma estrutura de uma matriz C do tipo Tmais o comprimento da matriz (isso seria essencialmente o que o std::array contêiner é), NOR é uma matriz C de estruturas de ponteiros para digitar Tmais o comprimento, mas é uma estrutura única que contém um ponteiro para digitarT e o comprimento , que é o número de elementos (do tipo T) no bloco de memória contíguo para o qual o ponteiro para o tipo Taponta! Dessa forma, a única sobrecarga que você adicionou usando umspansão as variáveis ​​para armazenar o ponteiro e o comprimento, e quaisquer funções de acessador de conveniência que você utilize que sejam spanfornecidas.

Isso é UNLIKE a std::array<>porque, na std::array<>verdade, aloca memória para todo o bloco contíguo, e é UNLIKE std::vector<>porque a std::vectoré basicamente apenas um std::arrayque também produz crescimento dinâmico (geralmente dobrando de tamanho) cada vez que é preenchido e você tenta adicionar algo a ele . A std::arrayé de tamanho fixo e a spannem gerencia a memória do bloco para o qual aponta, apenas aponta para o bloco de memória, sabe quanto tempo o bloco de memória tem, sabe qual é o tipo de dados em um array C na memória e fornece funções convenientes de acessador para trabalhar com os elementos nessa memória contígua .

Ele é parte do padrão C ++:

std::spanfaz parte do padrão C ++ a partir do C ++ 20. Você pode ler a documentação aqui: https://en.cppreference.com/w/cpp/container/span . Para ver como usar o Google absl::Span<T>(array, length)no C ++ 11 ou mais recente hoje , veja abaixo.

Descrições de resumo e referências principais:

  1. std::span<T, Extent>( Extent= "o número de elementos na sequência, ou std::dynamic_extentse dinâmico". Um período apenas aponta para a memória e facilita o acesso, mas NÃO o gerencia!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(observe que ele tem um tamanho fixoN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (cresce automaticamente de tamanho dinamicamente, conforme necessário):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Como posso usar spanno C ++ 11 ou posterior hoje ?

O Google abriu suas bibliotecas C ++ 11 internas na forma de sua biblioteca "Abseil". Esta biblioteca destina-se a fornecer recursos C ++ 14 a C ++ 20 e além, que funcionam no C ++ 11 e posterior, para que você possa usar os recursos de amanhã hoje. Eles dizem:

Compatibilidade com o padrão C ++

O Google desenvolveu muitas abstrações que correspondem ou se aproximam de recursos incorporados ao C ++ 14, C ++ 17 e além. O uso das versões Abseil dessas abstrações permite acessar esses recursos agora, mesmo que seu código ainda não esteja pronto para a vida em um mundo pós-C ++ 11.

Aqui estão alguns recursos e links importantes:

  1. Site principal: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Repositório do GitHub: https://github.com/abseil/abseil-cpp
  4. span.hcabeçalho e absl::Span<T>(array, length)classe de modelo: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
fonte
1
Acho que você traz informações importantes e úteis, obrigado!
Gui Lima