Che cos'è uno "span" e quando dovrei usarne uno?


237

Recentemente ho ricevuto suggerimenti per usare quelli span<T>nel mio codice, o ho visto alcune risposte qui sul sito che usano span- presumibilmente una sorta di contenitore. Ma - Non riesco a trovare nulla del genere nella libreria standard C ++ 17.

Allora, cos'è misterioso span<T>e perché (o quando) è una buona idea usarlo se non è standard?


std::spanè stato proposto nel 2017. Si applica a C ++ 17 o C ++ 20. Vedi anche P0122R5, span: viste senza limiti per sequenze di oggetti . Vuoi davvero scegliere come target quella lingua? Ci vorranno anni prima che i compilatori raggiungano.
JWW

6
@jww: gli span sono abbastanza utilizzabili con C ++ 11 ... come gsl::spanpiuttosto che std::span. Vedi anche la mia risposta qui sotto.
einpoklum,

Documentato anche su cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson

1
@KeithThompson: Non nel 2017 non era ...
einpoklum il

@jww Tutti i compilatori supportano std :: span <> ora in modalità C ++ 20. E span è disponibile da molte librerie di terze parti. Avevi ragione - erano anni: 2 anni per essere precisi.
Contango,

Risposte:


272

Che cos'è?

A span<T>è:

  • Un'astrazione molto leggera di una sequenza contigua di valori di tipo Tda qualche parte nella memoria.
  • Fondamentalmente a struct { T * ptr; std::size_t length; }con un sacco di metodi di convenienza.
  • Un tipo non proprietario (ovvero un "tipo di riferimento" anziché un "tipo di valore"): non alloca né distribuisce mai nulla e non mantiene in vita i puntatori intelligenti.

In precedenza era noto come array_viewe anche prima array_ref.

Quando dovrei usarlo?

Innanzitutto, quando non usarlo:

  • Non utilizzarlo nel codice che potrebbe basta prendere una qualsiasi coppia di inizio e fine iteratori, come std::sort, std::find_if, std::copye tutte quelle funzioni templated di super-generico.
  • Non utilizzarlo se si dispone di un contenitore di libreria standard (o di un contenitore Boost ecc.) Che si ritiene sia adatto al proprio codice. Non è destinato a soppiantare nessuno di loro.

Ora per quando usarlo effettivamente:

Usa span<T>(rispettivamente span<const T>) invece di un indipendente T*(rispettivamente const T*) per il quale hai il valore di lunghezza. Quindi, sostituisci funzioni come:

  void read_into(int* buffer, size_t buffer_size);

con:

  void read_into(span<int> buffer);

Perché dovrei usarlo? Perché è una buona cosa?

Oh, le campate sono fantastiche! Utilizzando un span...

  • significa che puoi lavorare con quella combinazione puntatore + lunghezza / inizio + fine puntatore come faresti con un contenitore di libreria standard elaborato e fantasioso, ad esempio:

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

    ... ma con nessuna delle spese generali che la maggior parte delle classi di container comporta.

  • consente al compilatore di fare più lavoro per te a volte. Ad esempio, questo:

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

    diventa questo:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ... che farà ciò che vorresti fosse fatto. Vedi anche la Linea guida P.5 .

  • è la ragionevole alternativa al passaggio const vector<T>&a funzioni quando si prevede che i dati siano contigui nella memoria. Non più essere sgridato dai potenti e potenti guru C ++!

  • facilita l'analisi statica, quindi il compilatore potrebbe essere in grado di aiutarti a rilevare bug stupidi.
  • consente la strumentazione di debug-compilation per il controllo dei limiti di runtime ( spani metodi di ie avranno un codice di controllo dei limiti entro #ifndef NDEBUG... #endif)
  • indica che il tuo codice (che utilizza lo span) non possiede la memoria puntata.

C'è ancora più motivazione per usare spans, che potresti trovare nelle linee guida di base di C ++ - ma cogli la deriva.

Perché non si trova nella libreria standard (a partire da C ++ 17)?

È nella libreria standard, ma solo da C ++ 20. Il motivo è che è ancora piuttosto nuovo nella sua forma attuale, concepito in collaborazione con il progetto delle linee guida di base C ++ , che prende forma solo dal 2015. (Sebbene, come sottolineano i commentatori, ha una storia precedente.)

Quindi come posso usarlo se non è ancora nella libreria standard?

Fa parte della Support Library delle Linee guida di supporto (GSL). implementazioni:

  • GSL di Microsoft / Neil Macintosh contiene un'implementazione autonoma:gsl/span
  • GSL-Lite è un'implementazione a intestazione singola dell'intero GSL (non è così grande, non ti preoccupare), incluso span<T>.

L'implementazione GSL generalmente presuppone una piattaforma che implementa il supporto C ++ 14 [ 14 ]. Queste implementazioni alternative a intestazione singola non dipendono dalle strutture GSL:

Si noti che queste diverse implementazioni di span hanno alcune differenze in quali metodi / funzioni di supporto vengono fornite; e possono anche differire in qualche modo dalla versione della libreria standard in C ++ 20.


Ulteriori letture: è possibile trovare tutti i dettagli e le considerazioni di progettazione nella proposta ufficiale finale prima di C ++ 17, P0122R7: span: viste senza limiti per sequenze di oggetti di Neal Macintosh e Stephan J. Lavavej. È un po 'lungo però. Inoltre, in C ++ 20, la semantica del confronto di span è cambiata (seguendo questo breve articolo di Tony van Eerd).


2
Sarebbe più sensato standardizzare un intervallo generale (supportando iteratore + sentinella e iteratore + lunghezza, forse anche iteratore + sentinella + lunghezza) e rendere lo span un semplice typedef. Perché, sai, è più generico.
Deduplicatore,

3
@Deduplicator: gli intervalli stanno arrivando in C ++, ma l'attuale proposta (di Eric Niebler) richiede il supporto per Concepts. Quindi non prima di C ++ 20.
einpoklum,

8
@ HảiPhạmLê: le matrici non si decompongono immediatamente in puntatori. prova a fare std::cout << sizeof(buffer) << '\n'e vedrai che otterrai 100 sizeof (int).
einpoklum,

4
@Jim std::arrayè un contenitore, possiede i valori. spannon possiede
Caleth il

3
@Jim: std::arrayè una bestia completamente diversa. La sua lunghezza è fissata in fase di compilazione ed è un tipo di valore anziché un tipo di riferimento, come ha spiegato Caleth.
einpoklum,

1

@einpoklum fa un ottimo lavoro nel presentare ciò che a spanè nella sua risposta qui . Tuttavia, anche dopo aver letto la sua risposta, è facile per qualcuno che è nuovo avere una sequenza di domande sul flusso di pensiero a cui non è stata data una risposta completa, come le seguenti:

  1. In che modo è spandiverso da un array C? Perché non usare solo uno di quelli? Sembra che sia solo uno di quelli con le dimensioni conosciute ...
  2. Aspetta, sembra un std::array, come è spandiverso da quello?
  3. Oh, questo mi ricorda, non è un std::vectorpo ' std::arraytroppo?
  4. Sono così confuso. :( Che cos'è un span?

Quindi, ecco qualche ulteriore chiarimento al riguardo:

PREVENTIVO DIRETTO DELLA SUA RISPOSTA - CON LE MIE AGGIUNTE IN GRASSETTO :

Che cos'è?

A span<T>è:

  • Un'astrazione molto leggera di una sequenza contigua di valori di tipo Tda qualche parte nella memoria.
  • Fondamentalmente una singola struttura { T * ptr; std::size_t length; }con un sacco di metodi di convenienza. (Si noti che questo è nettamente diverso dal std::array<>perché spanconsente metodi di accesso facilitato, comparabili a std::array, tramite un puntatore al tipoT e alla lunghezza (numero di elementi) del tipo T, mentre std::arrayè un contenitore reale che contiene uno o più valori di tipo T.)
  • Un tipo non proprietario (ovvero un "tipo di riferimento" anziché un "tipo di valore"): non alloca né distribuisce mai nulla e non mantiene in vita i puntatori intelligenti.

In precedenza era noto come array_viewe anche prima array_ref.

Quelle parti audaci sono fondamentali per la comprensione di qualcuno, quindi non perderle o fraintenderle! A spanNON è un array C di strutture, né è una struttura di un array C di tipo Tpiù la lunghezza dell'array (questo sarebbe essenzialmente quello che è il std::array contenitore ), NOR è un array C di strutture di puntatori digitare Tpiù la lunghezza, ma piuttosto è una singola struttura contenente un singolo puntatore da digitareT e la lunghezza , che è il numero di elementi (di tipo T) nel blocco di memoria contiguo a cui Tpunta il puntatore da digitare ! In questo modo, l'unico sovraccarico che hai aggiunto utilizzando aspansono le variabili per memorizzare il puntatore e la lunghezza e tutte le funzioni accessor di convenienza utilizzate che spanforniscono.

Questo è UNLIKE a std::array<>perché std::array<>effettivamente alloca memoria per l'intero blocco contiguo, ed è UNLIKE std::vector<>perché a std::vectorè fondamentalmente solo uno std::arrayche fa anche crescere dinamicamente (di solito raddoppiando di dimensioni) ogni volta che si riempie e si tenta di aggiungere qualcos'altro ad esso . A ha std::arraydimensioni fisse e a spannon gestisce nemmeno la memoria del blocco a cui punta, punta solo al blocco di memoria, sa quanto è lungo il blocco di memoria, sa quale tipo di dati è in un array C nella memoria e offre comode funzioni di accesso per lavorare con gli elementi in quella memoria contigua .

Si è parte dello standard C ++:

std::spanfa parte dello standard C ++ come C ++ 20. Puoi leggere la sua documentazione qui: https://en.cppreference.com/w/cpp/container/span . Per sapere come utilizzare Google absl::Span<T>(array, length)in C ++ 11 o versioni successive , vedere di seguito.

Descrizioni riepilogative e riferimenti chiave:

  1. std::span<T, Extent>( Extent= "il numero di elementi nella sequenza, o std::dynamic_extentse dinamico". Uno span punta solo alla memoria e ne facilita l'accesso, ma NON lo gestisce!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(nota che ha una dimensione fissaN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (cresce automaticamente in modo dinamico nelle dimensioni necessarie):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Come posso usare oggi spanin C ++ 11 o versioni successive ?

Google ha aperto le proprie librerie C ++ 11 interne sotto forma della libreria "Abseil". Questa libreria ha lo scopo di fornire da C ++ 14 a C ++ 20 e oltre funzionalità che funzionano in C ++ 11 e versioni successive, in modo da poter utilizzare le funzionalità di domani, oggi. Dicono:

Compatibilità con lo standard C ++

Google ha sviluppato molte astrazioni che corrispondono o coincidono strettamente con le funzioni incorporate in C ++ 14, C ++ 17 e oltre. L'uso delle versioni Abseil di queste astrazioni ti consente di accedere a queste funzionalità ora, anche se il tuo codice non è ancora pronto per la vita in un mondo post C ++ 11.

Ecco alcune risorse e collegamenti chiave:

  1. Sito principale: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Repository GitHub: https://github.com/abseil/abseil-cpp
  4. span.hintestazione e absl::Span<T>(array, length)classe modello: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189

1
Penso che porti informazioni importanti e utili, grazie!
Gui Lima,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.