Quand dois-je utiliser string_view dans une interface?

16

J'utilise une bibliothèque interne qui a été conçue pour imiter une bibliothèque C ++ proposée , et au cours des dernières années, je vois son interface passer de l'utilisation std::stringàstring_view .

Je change donc consciencieusement mon code, pour me conformer à la nouvelle interface. Malheureusement, ce que je dois transmettre est un paramètre std :: string et quelque chose qui est une valeur de retour std :: string. Donc, mon code a changé de quelque chose comme ceci:

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   api.setup (p1, special_number_to_string(p2));
}

à

void one_time_setup(const std::string & p1, int p2) {
   api_class api;
   const std::string p2_storage(special_number_to_string(p2));
   api.setup (string_view(&p1[0], p1.size()), string_view(&p2_storage[0], p2_storage.size()));
}

J'ai vraiment vois pas ce que ce changement m'a apporté en tant que client API, à part plus de code (pour éventuellement bousiller). L'appel d'API est moins sûr (car l'API ne possède plus le stockage pour ses paramètres), a probablement sauvé le travail de mon programme 0 (en raison des optimisations de déplacement que les compilateurs peuvent faire maintenant), et même s'il a économisé du travail, ce ne serait que quelques allocations qui ne seront pas et ne seront jamais faites après le démarrage ou dans une grande boucle quelque part. Pas pour cette API.

Cependant, cette approche semble suivre les conseils que je vois ailleurs, par exemple cette réponse :

En passant, depuis C ++ 17 vous devez éviter de passer un const std :: string & en faveur d'un std :: string_view:

Je trouve ce conseil surprenant, car il semble préconiser le remplacement universel d'un objet relativement sûr par un objet moins sûr (essentiellement un pointeur et une longueur glorifiés), principalement à des fins d'optimisation.

Alors, quand string_view devrait- il être utilisé et quand ne devrait-il pas l'être?

TED
la source
1
vous ne devriez jamais avoir à appeler std::string_viewdirectement le constructeur, vous devez simplement passer les chaînes à la méthode en prenant un std::string_viewdirectement et il se convertira automatiquement.
Mgetz
@Mgetz - Hmmm. Je n'utilise pas (encore) un compilateur C ++ 17 complet, donc c'est peut-être l'essentiel du problème. Pourtant, l'exemple de code ici semblait indiquer son requis, au moins lors de sa déclaration.
TED
4
Voir ma réponse, l'opérateur de conversion est dans l'en- <string>tête et se produit automatiquement. Ce code est trompeur et faux.
Mgetz
1
"avec une moins sûre" comment une tranche est-elle moins sûre qu'une référence de chaîne?
CodesInChaos
3
@TED ​​L'appelant peut libérer aussi facilement la chaîne vers laquelle votre référence pointe que la mémoire vers laquelle la tranche pointe.
CodesInChaos

Réponses:

18
  1. La fonctionnalité prenant la valeur doit-elle s'approprier la chaîne? Si c'est le cas, utilisez std::string(non const, non-ref). Cette option vous donne également le choix de déplacer explicitement une valeur si vous savez qu'elle ne sera plus jamais utilisée dans le contexte d'appel.
  2. La fonctionnalité lit-elle simplement la chaîne? Si c'est le cas, utilisez std::string_view(const, non-ref) c'est parce que string_viewpeut gérer std::stringet char*facilement sans problème et sans faire de copie. Cela devrait remplacer tous les const std::string&paramètres.

En fin de compte, vous ne devriez jamais avoir besoin d'appeler lestd::string_view constructeur comme vous. std::stringpossède un opérateur de conversion qui gère automatiquement la conversion.

Mgetz
la source
Juste pour clarifier un point, je pense qu'un tel opérateur de conversion s'occuperait également des pires problèmes de la vie, en s'assurant que la valeur de votre chaîne RHS reste autour pendant toute la durée de l'appel?
TED du
3
@TED ​​si vous ne faites que lire la valeur, la valeur survivra à l'appel. Si vous devenez propriétaire, il doit survivre à l'appel. C'est pourquoi j'ai abordé les deux cas. L'opérateur de conversion s'occupe simplement de std::string_viewfaciliter l'utilisation. Si un développeur l'utilise mal dans une situation propriétaire, c'est une erreur de programmation. std::string_viewest strictement non propriétaire.
Mgetz
Pourquoi const, non-ref? Le paramètre étant const est à l'usage spécifique, mais en général est raisonnable comme non-const. Et vous avez manqué 3. Peut accepter des tranches
v.oddou
Quel est le problème de passer const std::string_view &à la place de const std::string &?
ceztko
@ceztko c'est complètement inutile et ajoute une indirection supplémentaire lors de l'accès aux données.
Mgetz
15

A std::string_viewapporte certains des avantages d'un const char*à C ++: contrairement à std::string, un string_view

  • ne possède pas de mémoire,
  • n'alloue pas de mémoire,
  • peut pointer dans une chaîne existante à un certain décalage, et
  • a un niveau d'indirection de pointeur de moins qu'un a std::string&.

Cela signifie qu'un string_view peut souvent éviter les copies, sans avoir à traiter avec des pointeurs bruts.

Dans le code moderne, std::string_viewdevrait remplacer presque toutes les utilisations des const std::string&paramètres de fonction. Cela devrait être une modification compatible avec la source, car std::stringdéclare un opérateur de conversion àstd::string_view .

Ce n'est pas parce qu'une vue de chaîne n'aide pas dans votre cas d'utilisation spécifique où vous devez de toute façon créer une chaîne que c'est une mauvaise idée en général. La bibliothèque standard C ++ a tendance à être optimisée pour la généralité plutôt que pour la commodité. L'argument «moins sûr» ne tient pas, car il ne devrait pas être nécessaire de créer la vue chaîne vous-même.

amon
la source
2
L'inconvénient majeur std::string_viewest l'absence de c_str()méthode, ce qui entraîne des std::stringobjets intermédiaires inutiles qui doivent être construits et alloués. C'est particulièrement un problème dans les API de bas niveau.
Matthias
1
@Matthias C'est un bon point, mais je ne pense pas que ce soit un énorme inconvénient. Une vue chaîne vous permet de pointer dans une chaîne existante à un certain décalage. Cette sous-chaîne ne peut pas se terminer par zéro, vous avez besoin d'une copie pour cela. Une vue chaîne ne vous interdit pas de faire une copie. Il permet de nombreuses tâches de traitement de chaîne pouvant être effectuées avec des itérateurs. Mais vous avez raison de dire que les API qui ont besoin d'une chaîne C ne bénéficieront pas des vues. Une référence de chaîne peut alors être plus appropriée.
amon
@Matthias, string_view :: data () ne correspond-il pas à c_str ()?
Aelian
3
@Jeevaka une chaîne C doit être terminée par zéro, mais les données d'une vue de chaîne ne sont généralement pas terminées par zéro car elles pointent vers une chaîne existante. Par exemple, si nous avons une chaîne abcdef\0et une vue de chaîne qui pointe vers la cdesous - chaîne, il n'y a pas de caractère zéro après le e- la chaîne d'origine a un flà. La norme note également: «data () peut renvoyer un pointeur vers un tampon qui ne se termine pas par null. Par conséquent, c'est généralement une erreur de passer data () à une fonction qui prend juste un const charT * et attend une chaîne terminée par null. »
amon
1
@kayleeFrye_onDeck Les données sont déjà un pointeur char. Le problème avec les chaînes C n'est pas d'obtenir un pointeur char, mais qu'une chaîne C doit se terminer par null. Voir mon commentaire précédent pour un exemple.
amon
8

Je trouve ce conseil surprenant, car il semble préconiser le remplacement universel d'un objet relativement sûr par un objet moins sûr (essentiellement un pointeur et une longueur glorifiés), principalement à des fins d'optimisation.

Je pense que c'est un peu mal comprendre le but de cela. Bien qu'il s'agisse d'une "optimisation", vous devriez vraiment y voir un moyen de vous libérer de l'utilisation d'unstd::string .

Les utilisateurs de C ++ ont créé des dizaines de classes de chaînes différentes. Classes de chaînes de longueur fixe, classes optimisées pour l'authentification unique avec la taille du tampon comme paramètre de modèle, classes de chaînes qui stockent une valeur de hachage utilisée pour les comparer, etc. Certaines personnes utilisent même des chaînes basées sur COW. S'il y a une chose que les programmeurs C ++ aiment faire, c'est d'écrire des classes de chaînes.

Et cela ignore les chaînes qui sont créées et détenues par les bibliothèques C. Des nus char*, peut-être avec une taille quelconque.

Donc, si vous écrivez une bibliothèque et que vous en prenez une const std::string&, l'utilisateur doit maintenant prendre la chaîne qu'il utilisait et la copier dans a std::string. Peut-être des dizaines de fois.

Si vous souhaitez accéder à std::stringl'interface spécifique à la chaîne de, pourquoi devriez-vous copier la chaîne? C'est un tel gaspillage.

Les principales raisons de ne pas prendre a string_viewcomme paramètre sont:

  1. Si votre objectif ultime est de passer la chaîne à une interface qui prend une chaîne terminée par NUL ( fopen, etc.). std::stringest garanti d'être résilié NUL; string_viewn'est pas. Et il est très facile de sous-chaîne une vue pour la rendre non terminée par NUL; la sous- std::stringchaîne a copiera la sous-chaîne dans une plage terminée par NUL.

    J'ai écrit un type de style string_view spécial terminé par NUL pour exactement ce scénario. Vous pouvez effectuer la plupart des opérations, mais pas celles qui rompent son état terminé par NUL (découpage de la fin, par exemple).

  2. Problèmes à vie. Si vous avez vraiment besoin de copier cela std::stringou que le tableau de caractères dure plus longtemps que l'appel de fonction, il est préférable de le déclarer dès le début en prenant a const std::string &. Ou tout simplement std::stringcomme paramètre de valeur. De cette façon, s'ils ont déjà une telle chaîne, vous pouvez en revendiquer immédiatement la propriété, et l'appelant peut se déplacer dans la chaîne s'il n'a pas besoin d'en conserver une copie.

Nicol Bolas
la source
Est-ce vrai? La seule classe de chaîne standard que je connaissais en C ++ avant cela était std :: string. Il y a un certain support pour utiliser les char * comme "chaînes" pour la compatibilité descendante avec C, mais je n'ai presque jamais besoin de l'utiliser. Bien sûr, il existe de nombreuses classes tierces définies par l'utilisateur pour presque tout ce que vous pouvez imaginer, et des chaînes sont probablement incluses dans cela, mais je n'ai presque jamais à les utiliser.
TED
@TED: Ce n'est pas parce que vous "ne devez presque jamais les utiliser" que d' autres personnes ne les utilisent pas régulièrement. string_viewest un type de lingua franca qui peut fonctionner avec n'importe quoi.
Nicol Bolas
3
@TED: C'est pourquoi j'ai dit "C ++ en tant qu'environnement de programmation", par opposition à "C ++ en tant que langage / bibliothèque".
Nicol Bolas
2
@TED: " Je pourrais donc également dire" C ++ comme un environnement de programmation a des milliers de classes de conteneurs "? " Et c'est le cas. Mais je peux écrire des algorithmes qui fonctionnent avec des itérateurs, et toutes les classes de conteneurs qui suivent ce paradigme fonctionneront avec elles. En revanche, les «algorithmes» qui peuvent prendre n'importe quel tableau contigu de caractères étaient beaucoup plus difficiles à écrire. Avec string_view, c'est facile.
Nicol Bolas
1
@TED: Les tableaux de caractères sont un cas très spécial. Ils sont extrêmement courants et différents conteneurs de caractères contigus ne diffèrent que par la façon dont ils gèrent leur mémoire, pas par la façon dont vous parcourez les données. Il est donc logique d'avoir un seul type de gamme lingua franca qui peut couvrir tous ces cas sans avoir à utiliser un modèle. La généralisation au-delà est la province de la gamme TS et des modèles.
Nicol Bolas