Qu'est-ce qu'un «span» et quand dois-je en utiliser un?

237

Récemment, j'ai reçu des suggestions d'utilisation de span<T>'s dans mon code, ou j'ai vu quelques réponses ici sur le site qui utilisent span' s - soi-disant une sorte de conteneur. Mais - je ne trouve rien de tel dans la bibliothèque standard C ++ 17.

Alors, quel est ce mystérieux span<T>, et pourquoi (ou quand) est-ce une bonne idée de l'utiliser si ce n'est pas standard?

einpoklum
la source
std::spana été proposé en 2017. Il s'applique au C ++ 17 ou au C ++ 20. Voir également P0122R5, vues span: sans limites pour les séquences d'objets . Voulez-vous vraiment cibler cette langue? Il faudra des années avant que les compilateurs ne rattrapent leur retard.
jww
6
@jww: les span sont tout à fait utilisables avec C ++ 11 ... gsl::spanplutôt que std::span. Voir aussi ma réponse ci-dessous.
einpoklum
Également documenté sur cppreference.com: en.cppreference.com/w/cpp/container/span
Keith Thompson
1
@KeithThompson: Pas en 2017 ce n'était pas ...
einpoklum
@jww Tous les compilateurs prennent en charge std :: span <> maintenant en mode C ++ 20. Et span est disponible à partir de nombreuses bibliothèques tierces. Vous aviez raison - c'était des années: 2 ans pour être précis.
Contango

Réponses:

272

Qu'Est-ce que c'est?

A span<T>est:

  • Une abstraction très légère d'une séquence contiguë de valeurs de type Tquelque part en mémoire.
  • Fondamentalement, struct { T * ptr; std::size_t length; }avec un tas de méthodes pratiques.
  • Un type sans propriétaire (c'est-à-dire un "type de référence" plutôt qu'un "type de valeur"): il n'alloue ni ne désalloue rien et ne maintient pas les pointeurs intelligents en vie.

Il était auparavant connu sous le nom de array_viewet même plus tôt array_ref.

Quand devrais-je l'utiliser?

Tout d'abord, quand ne pas l'utiliser:

  • Ne pas utiliser dans le code qui pourrait juste prendre une paire de itérateurs de début et de fin, comme std::sort, std::find_if, std::copyet toutes les fonctions de super-générique basé sur un modèle.
  • Ne l'utilisez pas si vous avez un conteneur de bibliothèque standard (ou un conteneur Boost, etc.) dont vous savez qu'il convient à votre code. Il n'est pas destiné à les remplacer.

Maintenant, quand l'utiliser réellement:

Utilisez span<T>(respectivement span<const T>) au lieu d'un support indépendant T*(respectivement const T*) pour lequel vous avez la valeur de longueur. Donc, remplacez des fonctions comme:

  void read_into(int* buffer, size_t buffer_size);

avec:

  void read_into(span<int> buffer);

Pourquoi devrais-je l'utiliser? Pourquoi est-ce une bonne chose?

Oh, les portées sont géniales! À l'aide d'un span...

  • signifie que vous pouvez travailler avec cette combinaison pointeur + longueur / pointeur de début + fin comme vous le feriez avec un conteneur de bibliothèque standard fantaisie et pimped-out, par exemple:

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

    ... mais avec absolument aucun des frais généraux encourus par la plupart des classes de conteneurs.

  • laisse parfois le compilateur faire plus de travail pour vous. Par exemple, ceci:

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

    devient ceci:

    int buffer[BUFFER_SIZE];
    read_into(buffer);
    

    ... qui fera ce que vous voudriez qu'il fasse. Voir également la ligne directrice P.5 .

  • est l'alternative raisonnable au passage const vector<T>&aux fonctions lorsque vous vous attendez à ce que vos données soient contiguës en mémoire. Plus besoin d'être grondé par les gourous C ++ haut et puissants!

  • facilite l'analyse statique, de sorte que le compilateur pourrait être en mesure de vous aider à détecter les bogues idiots.
  • permet une instrumentation de compilation de débogage pour la vérification des limites à l'exécution (c'est-à-dire que spanles méthodes auront du code de vérification des limites dans #ifndef NDEBUG... #endif)
  • indique que votre code (qui utilise la plage) ne possède pas la mémoire pointée.

Il y a encore plus de motivation pour utiliser spans, que vous pouvez trouver dans les directives de base C ++ - mais vous attrapez la dérive.

Pourquoi n'est-il pas dans la bibliothèque standard (à partir de C ++ 17)?

Il se trouve dans la bibliothèque standard - mais uniquement depuis C ++ 20. La raison en est qu'il est encore assez nouveau dans sa forme actuelle, conçu en collaboration avec le projet de lignes directrices de base C ++ , qui ne prend forme que depuis 2015. (Bien que les commentateurs le soulignent, il a une histoire antérieure.)

Alors, comment puis-je l'utiliser si ce n'est pas encore dans la bibliothèque standard?

Il fait partie de la bibliothèque de support (GSL) de Core Guidelines . Implémentations:

  • Le GSL de Microsoft / Neil Macintosh contient une implémentation autonome:gsl/span
  • GSL-Lite est une implémentation à un seul en-tête de l'ensemble du GSL (ce n'est pas si gros, ne vous inquiétez pas), y compris span<T>.

L'implémentation GSL suppose généralement une plate-forme qui implémente le support C ++ 14 [ 14 ]. Ces implémentations alternatives à en-tête unique ne dépendent pas des fonctionnalités GSL:

Notez que ces différentes implémentations span ont quelques différences dans les méthodes / fonctions de support avec lesquelles elles sont fournies; et ils peuvent également différer quelque peu de la version entrant dans la bibliothèque standard en C ++ 20.


Pour en savoir plus: Vous pouvez trouver tous les détails et les considérations de conception dans la proposition officielle finale avant C ++ 17, P0122R7: span: vues sans limites pour les séquences d'objets de Neal Macintosh et Stephan J. Lavavej. C'est un peu long cependant. De plus, en C ++ 20, la sémantique de comparaison des intervalles a changé (suite à ce court article de Tony van Eerd).

einpoklum
la source
2
Il serait plus logique de standardiser une plage générale (prenant en charge itérateur + sentinelle et itérateur + longueur, peut-être même itérateur + sentinelle + longueur) et de faire de span un simple typedef. Parce que, vous savez, c'est plus générique.
Déduplicateur
3
@Deduplicator: Les gammes arrivent en C ++, mais la proposition actuelle (par Eric Niebler) nécessite la prise en charge de Concepts. Donc pas avant C ++ 20.
einpoklum
8
@ HảiPhạmLê: Les tableaux ne se désintègrent pas immédiatement en pointeurs. essayez de faire std::cout << sizeof(buffer) << '\n'et vous verrez que vous obtenez 100 sizeof (int).
einpoklum
4
@Jim std::arrayest un conteneur, il possède les valeurs. spann'est pas propriétaire
Caleth
3
@ Jim: std::arrayest une bête complètement différente. Sa longueur est fixée au moment de la compilation et c'est un type de valeur plutôt qu'un type de référence, comme l'a expliqué Caleth.
einpoklum
1

@einpoklum fait un assez bon travail d'introduire quelle spanest sa réponse ici . Cependant, même après avoir lu sa réponse, il est facile pour quelqu'un qui ne connaît pas le domaine d'avoir toujours une séquence de questions de réflexion qui ne sont pas entièrement répondues, telles que les suivantes:

  1. Comment est un spandifférent d'un tableau C? Pourquoi ne pas simplement en utiliser un? Il semble que ce ne soit que l'un de ceux dont la taille est également connue ...
  2. Attendez, cela ressemble à un std::array, en quoi est-ce spandifférent de cela?
  3. Oh, ça me rappelle, n'est-ce pas std::vectorcomme un std::arrayaussi?
  4. Je suis tellement confus. :( Qu'est-ce qu'un span?

Alors, voici quelques précisions supplémentaires à ce sujet:

CITATION DIRECTE DE SA RÉPONSE - AVEC MES ADDITIONS EN GRAS :

Qu'Est-ce que c'est?

A span<T>est:

  • Une abstraction très légère d'une séquence contiguë de valeurs de type Tquelque part en mémoire.
  • Fondamentalement, une seule structure { T * ptr; std::size_t length; }avec un tas de méthodes pratiques. (Remarquez que ceci est distinctement différent du std::array<>fait que un spanpermet des méthodes d'accesseur pratiques, comparables à std::array, via un pointeur vers typeT et longueur (nombre d'éléments) de type T, alors qu'il std::arrays'agit d'un conteneur réel qui contient une ou plusieurs valeurs de type T.)
  • Un type sans propriétaire (c'est-à-dire un "type de référence" plutôt qu'un "type de valeur"): il n'alloue ni ne désalloue rien et ne maintient pas les pointeurs intelligents en vie.

Il était auparavant connu sous le nom de array_viewet même plus tôt array_ref.

Ces parties audacieuses sont essentielles à la compréhension, alors ne les manquez pas ou ne les lisez pas! A spann'est PAS un C-tableau de structures, ni un struct d'un C-tableau de type Tplus la longueur du tableau (ce serait essentiellement ce que le std::array conteneur est), NI n'est-ce pas un C-tableau de structures de pointeurs à taper Tplus la longueur, mais c'est plutôt une structure unique contenant un seul pointeur à taperT , et la longueur , qui est le nombre d'éléments (de type T) dans le bloc de mémoire contigu vers lequel Tpointe le pointeur à taper ! De cette façon, la seule surcharge que vous avez ajoutée en utilisant unspansont les variables pour stocker le pointeur et la longueur, et toutes les fonctions d'accesseur de commodité que vous utilisez et qui le spanfournissent.

Il ne s'agit pas d'un std::array<>parce que le std::array<>alloue réellement de la mémoire pour le bloc contigu entier, et il n'est pas comme std::vector<>parce qu'un std::vectorest fondamentalement juste un std::arrayqui fait également une croissance dynamique (généralement doublant de taille) à chaque fois qu'il se remplit et que vous essayez d'ajouter quelque chose d'autre. . A std::arrayest de taille fixe, et un spanne gère même pas la mémoire du bloc il pointe, il seulement des points au bloc de mémoire, sait combien de temps le bloc de mémoire est, sait ce type de données est dans un C-tableau dans la mémoire et fournit des fonctions d'accesseur pratiques pour travailler avec les éléments de cette mémoire contiguë .

Il fait partie du standard C ++:

std::spanfait partie de la norme C ++ à partir de C ++ 20. Vous pouvez lire sa documentation ici: https://en.cppreference.com/w/cpp/container/span . Pour voir comment utiliser Google absl::Span<T>(array, length)en C ++ 11 ou plus tard aujourd'hui , voir ci-dessous.

Descriptions sommaires et références clés:

  1. std::span<T, Extent>( Extent= "le nombre d'éléments dans la séquence, ou std::dynamic_extents'il est dynamique". Une plage pointe simplement vers la mémoire et facilite l'accès, mais ne la gère PAS!):
    1. https://en.cppreference.com/w/cpp/container/span
  2. std::array<T, N>(remarquez qu'il a une taille fixeN !):
    1. https://en.cppreference.com/w/cpp/container/array
    2. http://www.cplusplus.com/reference/array/array/
  3. std::vector<T> (augmente automatiquement la taille de façon dynamique si nécessaire):
    1. https://en.cppreference.com/w/cpp/container/vector
    2. http://www.cplusplus.com/reference/vector/vector/

Comment puis-je utiliser spanen C ++ 11 ou plus tard aujourd'hui ?

Google a open source leurs bibliothèques C ++ 11 internes sous la forme de leur bibliothèque "Abseil". Cette bibliothèque est destinée à fournir des fonctionnalités C ++ 14 à C ++ 20 et au-delà qui fonctionnent en C ++ 11 et versions ultérieures, afin que vous puissiez utiliser les fonctionnalités de demain dès aujourd'hui. Ils disent:

Compatibilité avec la norme C ++

Google a développé de nombreuses abstractions qui correspondent ou correspondent étroitement aux fonctionnalités incorporées dans C ++ 14, C ++ 17 et au-delà. L'utilisation des versions Abseil de ces abstractions vous permet d'accéder à ces fonctionnalités maintenant, même si votre code n'est pas encore prêt à vivre dans un monde post-C ++ 11.

Voici quelques ressources et liens clés:

  1. Site principal: https://abseil.io/
  2. https://abseil.io/docs/cpp/
  3. Référentiel GitHub: https://github.com/abseil/abseil-cpp
  4. span.hen-tête et absl::Span<T>(array, length)classe de modèle: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L189
Gabriel Staples
la source
1
Je pense que vous apportez des informations importantes et utiles, merci!
Gui Lima