303 votes

Qu'est-ce qu'un "span" et quand dois-je l'utiliser ?

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

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

1 votes

std::span a été proposé en 2017. Elle s'applique à C++17 ou C++20. Voir aussi P0122R5, span : vues sécurisées par les limites pour les séquences d'objets . Voulez-vous vraiment cibler ce langage ? Il faudra des années avant que les compilateurs ne rattrapent leur retard.

6 votes

@jww : les span's sont tout à fait utilisables avec C++11... comme gsl::span plutôt que std::span . Voir aussi ma réponse ci-dessous.

0 votes

Également documenté sur cppreference.com : fr.cppreference.com/w/cpp/container/span

356voto

einpoklum Points 2893

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 T quelque part dans la mémoire.
  • En gros, un struct { T * ptr; std::size_t length; } avec un tas de méthodes pratiques.
  • Un type non 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 jamais rien et ne maintient pas les pointeurs intelligents en vie.

Il était autrefois connu sous le nom de array_view et même plus tôt comme array_ref .

Quand dois-je l'utiliser ?

Premièrement, lorsque pas pour l'utiliser :

  • Ne l'utilisez pas dans du code qui pourrait simplement prendre n'importe quelle paire d'itérateurs de début et de fin, comme par exemple std::sort , std::find_if , std::copy et toutes ces fonctions modèles super-génériques.
  • Ne l'utilisez pas si vous avez un conteneur de bibliothèque standard (ou un conteneur Boost, etc.) que vous savez être le plus adapté à votre code. Il n'est pas destiné à supplanter l'un d'entre eux.

Maintenant, il faut savoir quand l'utiliser :

Utilisez span<T> (respectivement, span<const T> ) au lieu d'un T* (respectivement const T* ) lorsque la longueur ou la taille allouée compte également. Ainsi, remplacez les 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 travées sont géniales ! En utilisant 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, par exemple :

    • for (auto& x : my_span) { /* do stuff */ }
    • std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
    • std::ranges::find_if(my_span, some_predicate); (en C++20)

    ... mais sans aucun des frais généraux que la plupart des classes de conteneurs encourent.

  • 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 voulez qu'il fasse. Voir aussi 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. Ne vous faites plus gronder par les gourous du C++ !

  • facilite l'analyse statique, de sorte que le compilateur peut être en mesure de vous aider à détecter des bogues stupides.

  • permet l'instrumentation de la compilation de débogage pour le contrôle des limites à l'exécution (c.-à-d. span Les méthodes de l'entreprise seront dotées d'un code de contrôle des limites au sein de l'entreprise. #ifndef NDEBUG ... #endif )

  • indique que votre code (qui utilise le span) ne possède pas la mémoire pointée.

Il y a encore plus de motivation pour utiliser span que vous pouvez trouver dans le Directives de base du C++ - mais vous voyez ce que je veux dire.

Mais est-il dans la bibliothèque standard ?

éditer : Oui, std::span a été ajouté à C++ avec la version C++20 du langage !

Pourquoi seulement en C++20 ? Eh bien, bien que l'idée ne soit pas nouvelle - sa forme actuelle a été conçue en conjonction avec l'initiative de l Directives de base du C++ projet, qui n'a commencé à prendre forme qu'en 2015. Il a donc fallu du temps.

Alors comment l'utiliser si j'écris du C++17 ou antérieur ?

Il fait partie de la Directives de base La bibliothèque de soutien de l'entreprise (GSL). Implémentations :

  • Microsoft / Neil Macintosh's GSL contient une implémentation autonome : gsl/span
  • GSL-Lite est une implémentation à en-tête unique de l'ensemble de la GSL (elle n'est pas si grande, ne vous inquiétez pas), notamment span<T> .

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

Il est à noter que ces différentes implémentations de span présentent quelques différences quant aux méthodes/fonctions de support qu'elles comportent ; elles peuvent également différer quelque peu de la version adoptée 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 sécurisées pour les séquences d'objets par Neal Macintosh et Stephan J. Lavavej. C'est un peu long cependant. De plus, dans C++20, la sémantique de la comparaison de l'étendue a été modifiée (suite à ce court document par Tony van Eerd).

1 votes

N'est-ce pas ? read_into(int* from, int* to) serait plus conforme à l'approche habituelle (basée sur les itérateurs) de la bibliothèque standard ?

0 votes

@DanielJour : Je voulais un exemple minimaliste. Votre variante et la mienne des fonctions ne seraient probablement pas quelque chose que vous mettriez réellement dans une bibliothèque de toute façon. De plus, notez qu'avec votre variante, il n'est pas possible de savoir combien il faut lire, donc peut-être que vous voulez dire read_into(int* from, int* to, size_t n) ? C'est mentionné dans le document des lignes directrices de base comme un "mauvais exemple" en fait.

0 votes

@einpoklum oh, non, ce n'est pas ce que je voulais dire. Une prise différente : read_into(int* begin, int* end)

21voto

Gabriel Staples Points 1804

A span<T> c'est ça :

template <typename T>
struct span
{
    T * ptr_to_array;   // pointer to a contiguous C-style array of data
                        // (which memory is NOT allocated or deallocated 
                        // by the span)
    std::size_t length; // number of elements in the array

    // Plus a bunch of constructors and convenience accessor methods here
}

Il s'agit d'une enveloppe légère autour d'un tableau de style C, préférée par les développeurs C++ chaque fois qu'ils utilisent des bibliothèques C et qu'ils veulent les envelopper avec un conteneur de données de style C++ pour la "sécurité des types", l'"esprit C++" et la "convivialité".)


Pour aller plus loin :

@einpoklum fait un très bon travail pour présenter ce qu'est un span est dans sa réponse ici . Cependant, même après avoir lu sa réponse, il est facile pour une personne novice en matière de spans d'avoir encore une série de questions à l'emporte-pièce auxquelles il n'est pas possible de répondre complètement, telles que les suivantes :

  1. Comment un span différent d'un tableau C ? Pourquoi ne pas simplement utiliser l'un d'entre eux ? Il semble que c'est juste un de ceux-là avec la taille connue aussi...
  2. Attends, ça ressemble à un std::array comment un span différent de celui-là ?
  3. Oh, ça me fait penser, n'est-ce pas un std::vector comme un std::array aussi ?
  4. Je suis tellement confus. :( Qu'est-ce qu'un span ?

Voici donc quelques éclaircissements supplémentaires à ce sujet :

CITATION DIRECTE DE SA RÉPONSE - AVEC MES ADDITIONS et commentaires entre parenthèses en caractères gras et Je souligne en italique :

Qu'est-ce que c'est ?

A span<T> est :

  • Une abstraction très légère d'un séquence de valeurs contiguës de type T quelque part dans la mémoire.
  • En gros, un simple struct { T * ptr; std::size_t length; } avec un tas de méthodes pratiques. (Notez que ceci est très différent de std::array<> car un span permet des méthodes d'accès pratiques, comparables à std::array par l'intermédiaire d'un pointeur vers le type T et longueur (nombre d'éléments) de type T alors que std::array est un conteneur réel qui contient un ou plusieurs valeurs de type T .)
  • A type de non-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 jamais rien et ne garde pas les pointeurs intelligents en vie.

Il était autrefois connu sous le nom de array_view et même plus tôt comme array_ref .

Ces parties en gras sont critique à la compréhension de chacun, alors ne les ratez pas et ne les lisez pas mal ! A span n'est PAS un tableau C de structs, ni un struct d'un tableau C de type T plus la longueur du tableau (c'est essentiellement ce que fait l'option std::array conteneur est), NOR est un tableau C de structs de pointeurs de type T plus la longueur, mais il s'agit plutôt d'une simple struct contenant un seul pointeur vers le type T et le longueur qui est le nombre d'éléments (de type T ) dans le bloc de mémoire contigu que le pointeur du type T pointe ! De cette façon, le seul surcoût que vous avez ajouté en utilisant un fichier span sont les variables permettant de stocker le pointeur et la longueur, ainsi que toutes les fonctions d'accès de commodité que vous utilisez et qui sont utilisées par l'utilisateur. span fournit.

Cela n'a rien à voir avec un std::array<> parce que le std::array<> alloue en fait de la mémoire pour le bloc contigu entier, et c'est UNLIKE std::vector<> car un std::vector est en fait juste un std::array qui fait aussi croissance dynamique (dont la taille double généralement) chaque fois qu'il se remplit et que vous essayez d'y ajouter quelque chose. A std::array est de taille fixe, et a span ne gère même pas la mémoire du bloc vers lequel il pointe, il se contente de pointer vers le bloc de mémoire, de connaître la longueur du bloc de mémoire, de savoir quel type de données se trouve dans un tableau C de la mémoire et de fournir des fonctions d'accès pratiques pour travailler avec les éléments de cette mémoire contiguë. .

Il est qui fait partie de la norme C++ :

std::span fait partie de la norme C++ à partir de C++20. Vous pouvez lire sa documentation ici : https://en.cppreference.com/w/cpp/container/span . Pour savoir comment utiliser les services de Google absl::Span<T>(array, length) en C++11 ou plus 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_extent si dynamique". Une portée juste Les points suivants la mémoire et la rend facile d'accès, mais ne la gère PAS !) :
  2. https://en.cppreference.com/w/cpp/container/span
  3. std::array<T, N> (remarquez qu'il y a un Correction de taille N !) :
  4. https://en.cppreference.com/w/cpp/container/array
  5. http://www.cplusplus.com/reference/array/array/
  6. std::vector<T> (dont la taille augmente automatiquement et dynamiquement si nécessaire) :
  7. https://en.cppreference.com/w/cpp/container/vector
  8. http://www.cplusplus.com/reference/vector/vector/

Comment puis-je utiliser span en C++11 ou plus aujourd'hui ?

Google a mis en libre accès ses bibliothèques internes C++11 sous la forme de sa bibliothèque "Abseil". Cette bibliothèque est destinée à fournir des fonctionnalités C++14 à C++20 et au-delà qui fonctionnent dans C++11 et plus, afin que vous puissiez utiliser les fonctionnalités de demain, aujourd'hui. C'est ce qu'ils disent :

Compatibilité avec la norme C++

Google a développé de nombreuses abstractions qui correspondent ou s'approchent des 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 dè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. Dépôt GitHub : https://github.com/abseil/abseil-cpp
  4. span.h l'en-tête, et absl::Span<T>(array, length) classe modèle : https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L153

Autres références :

  1. Structures avec variables modèles en C++
  2. Wikipédia : Classes C++
  3. visibilité par défaut des membres des classes/structures C++

En rapport :

  1. [une autre de mes réponses sur les modèles et les travées]. Comment faire une travée de travées

4 votes

Je ne recommanderais vraiment pas d'utiliser tout le rappel pour obtenir un cours de span.

0 votes

Je l'ai. Le plus gros avantage est la légèreté.

0 votes

@yushang, du point de vue d'un développeur C++, je pense que le plus gros avantage n'est pas "léger", mais plutôt : "Vous avez maintenant une enveloppe autour d'un conteneur qui conserve sa taille à l'intérieur, contrairement aux tableaux C, qui ne connaissent pas et ne transportent pas d'informations sur leur propre taille. En tant que développeur embarqué, ayant moi-même plus d'expérience en C qu'en C++, je préfère généralement utiliser des tableaux C bruts de toute façon, sur un span.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X