web-dev-qa-db-fra.com

Quand dois-je utiliser string_view dans une interface?

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

J'ai donc consciencieusement changé 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()));
}

Je vraiment ne 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.

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

19
T.E.D.
  1. La fonctionnalité prenant la valeur doit-elle s'approprier la chaîne? Si oui, 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 oui, utilisez std::string_view (const, non-ref) c'est parce que string_view peut gérer std::string et 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 le std::string_view constructeur comme vous. std::string a un opérateur de conversion qui gère automatiquement la conversion.

19
Mgetz

UNE std::string_view apporte certains des avantages d'un const char* en C++: contrairement à std::string, une chaîne_vue

  • 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 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_view devrait remplacer presque toutes les utilisations de const std::string& paramètres de fonction. Cela devrait être une modification compatible avec la source, car std::string déclare un opérateur de conversion à std::string_view.

Tout simplement 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 ne signifie pas 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.

15
amon

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'un std::string.

Les utilisateurs de C++ ont créé des dizaines de différentes classes de chaînes. 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. char* Nus, peut-être avec une taille quelconque.

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

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

Les principales raisons de ne pas prendre un string_view Comme paramètre sont:

  1. Si votre but ultime est de passer la chaîne à une interface qui prend une chaîne terminée par NUL (fopen, etc.). std::string Est garanti d'être terminé NUL; string_view Ne l'est pas. Et il est très facile de sous-chaîne une vue pour la rendre non terminée par NUL; la sous-chaîne d'un std::string 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 ce std::string Ou sinon le tableau de caractères survit à l'appel de la fonction, il est préférable de le déclarer à l'avance en prenant un const std::string &. Ou juste un std::string Comme 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.

8
Nicol Bolas