Conseil par défaut pour l’utilisation de littéraux de chaîne de style C et la construction d’objects std :: ssortingng non nommés?

C ++ 14 a donc introduit un certain nombre de littéraux définis par l’utilisateur à utiliser, dont le suffixe littéral “s” , pour la création d’objects std::ssortingng . Selon la documentation, son comportement est identique à celui de la construction d’un object std::ssortingng , comme suit:

 auto str = "Hello World!"s; // RHS is equivalent to: std::ssortingng{ "Hello World!" } 

Bien sûr, la construction d’un object std::ssortingng sans nom peut être réalisée avant C ++ 14, mais comme la méthode C ++ 14 est beaucoup plus simple, je pense qu’un plus grand nombre de personnes envisageront effectivement de construire std::ssortingng objects std::ssortingng sur le vif. pourquoi j’ai pensé qu’il était logique de poser cette question.

Ma question est donc simple: dans quels cas c’est une bonne (ou une mauvaise) idée de construire un object std::ssortingng sans nom, au lieu d’utiliser simplement un littéral de chaîne de style C?


Exemple 1:

Considérer ce qui suit:

 void foo(std::ssortingng arg); foo("bar"); // option 1 foo("bar"s); // option 2 

Si je ne me trompe pas, la première méthode appelle la surcharge de constructeur appropriée de std::ssortingng pour créer un object dans la scope de foo , et la seconde méthode commence par construire un object chaîne non nommé, puis par move-construct foo argument de cela. Bien que je sois sûr que les compilateurs optimisent très bien ce genre de choses, la deuxième version semble toutefois impliquer un mouvement supplémentaire, par opposition à la première alternative (ce n’est pas comme si un déménagement coûtait cher). Mais encore une fois, après avoir compilé cela avec un compilateur raisonnable, les résultats finaux seront très probablement très optimisés et exempts de toute redondance / déplacement / copie.

De plus, que se passe-t-il si foo est surchargé pour accepter des références rvalue? Dans ce cas, je pense qu’il serait logique d’appeler foo("bar"s) , mais je peux me tromper.


Exemple 2:

Considérer ce qui suit:

 std::cout << "Hello World!" << std::endl; // option 1 std::cout << "Hello World!"s << std::endl; // option 2 

Dans ce cas, l’object std::ssortingng est probablement passé à l’opérateur de cout via la référence rvalue, et la première option passe probablement un pointeur; les deux opérations sont donc très économiques, mais le second a le coût supplémentaire de la construction d’un object. premier. C’est probablement une façon plus sûre d’y aller cependant (?).


Bien entendu, dans tous les cas, la construction d’un object std::ssortingng peut donner lieu à une allocation de tas, ce qui peut entraîner un rejet. Par conséquent, la sécurité des exceptions doit également être prise en compte. Ceci est plus un problème dans le deuxième exemple bien que, comme dans le premier exemple, un object std::ssortingng sera construit dans les deux cas de toute façon. En pratique, obtenir une exception lors de la construction d’un object chaîne est très improbable, mais pourrait néanmoins constituer un argument valable dans certains cas.

Si vous pouvez penser à plus d’exemples à considérer, veuillez les inclure dans votre réponse. Je suis intéressé par un conseil général concernant l’utilisation d’objects std::ssortingng non nommés, et pas seulement ces deux cas particuliers. Je ne les ai inclus que pour souligner certaines de mes reflections sur ce sujet.

De plus, si quelque chose ne va pas, n’hésitez pas à me corriger car je ne suis en aucun cas un expert en C ++. Les comportements que je décris ne sont que des suppositions sur la façon dont les choses fonctionnent et je ne les ai pas fondées sur des recherches ou des expériences réelles.

Dans quels cas c’est une bonne (ou mauvaise) idée de construire un object std::ssortingng non nommé, au lieu d’utiliser simplement un littéral de chaîne de style C?

Un std::ssortingng – littéral est une bonne idée lorsque vous voulez spécifiquement une variable de type std::ssortingng , que ce soit pour

  • modifier la valeur ultérieurement ( auto s = "123"s; s += '\n'; )

  • l’interface plus riche, intuitive et moins sujette aux erreurs (sémantique des valeurs, iterators, find , size etc.)

    • sémantique de valeur signifie == , < copie, etc., travail sur les valeurs, contrairement à la sémantique de pointeur / par référence après la décomposition des littéraux de chaîne C en const char* s
  • appeler some_templated_function("123"s) assurerait de manière concise une instanciation , l'argument pouvant être traité à l'aide de la sémantique de valeur en interne

    • de toute façon, si vous connaissez d'autres codes instanciant le modèle pour std::ssortingng et que la complexité de ce dernier est importante par rapport à vos contraintes de ressources, vous pouvez également passer un std::ssortingng pour éviter l'instanciation inutile de const char* , mais c'est rare avoir besoin de soins
  • valeurs contenant des NUL incorporés

Un littéral de chaîne de style C peut être préféré où:

  • une sémantique de type pointeur est souhaitée (ou du moins pas un problème)

  • la valeur ne sera transmise qu'aux fonctions qui attendent const char* toute façon, ou std::ssortingng temporaries std::ssortingng seront construites de toute façon et vous ne vous souciez pas de donner à l'optimiseur de compilateur un obstacle supplémentaire à franchir pour réaliser le temps de compilation ou de chargement construction s'il est possible de réutiliser la même instance std::ssortingng (par exemple, lors du passage à des fonctions par const -reference) - là encore, il est rare d'avoir besoin de s'en préoccuper.

  • (un autre hack rare et méchant) vous exploitez en quelque sorte le comportement de groupement des chaînes de votre compilateur, par exemple s'il garantit que pour une unité de traduction donnée, le caractère const char* des littéraux de chaîne ne différera que (mais bien sûr toujours) si le texte diffère

    • vous ne pouvez pas vraiment obtenir la même chose de std::ssortingng .data() / .c_str() , car la même adresse peut être associée à un texte différent (et à différentes instances de std::ssortingng ) lors de l'exécution du programme, et à std::ssortingng tampons de std::ssortingng à des adresses distinctes peuvent contenir le même texte
  • vous bénéficiez du fait que le pointeur rest valide après que std::ssortingng ait quitté la scope et soit détruit (par exemple, enum My_Enum { Zero, One }; donné enum My_Enum { Zero, One }; - const char* str(My_Enum e) { return e == Zero ? "0" : "1"; } est sûr, mais const char* str(My_Enum e) { return e == Zero ? "0"s.c_str() : "1"s.c_str(); } n'est pas et std::ssortingng str(My_Enum e) { return e == Zero ? "0"s : "1"s; } sent le pessimisme prématuré en utilisant toujours l'allocation dynamic (sans SSO, ou pour un texte plus long))

  • vous tirez parti de la concaténation au moment de la compilation des littéraux de chaîne C adjacents (par exemple, "abc" "xyz" devient un caractère contigu "abcxyz" const char[] littéral "abcxyz" ) - cela est particulièrement utile dans les substitutions de macros

  • vous êtes limité en mémoire et / ou ne voulez pas risquer une exception ou un blocage lors de l'allocation dynamic de mémoire

Discussion

[basic.ssortingng.literals] 21.7 listes:

ssortingng operator "" s(const char* str, size_t len);

Renvoie: ssortingng{str,len}

Fondamentalement, l'utilisation de ""s appelle une fonction qui renvoie std::ssortingng par valeur. De manière cruciale, vous pouvez lier une référence const ou rvalue, mais pas une référence lvalue.

Lorsqu'il est utilisé pour appeler void foo(std::ssortingng arg); , arg sera en effet être construit construit.

De plus, que se passe-t-il si foo est surchargé pour accepter des références rvalue? Dans ce cas, je pense qu'il serait logique d'appeler foo ("bar" s), mais je peux me tromper.

Peu importe ce que vous choisissez. En ce qui concerne la maintenance, si foo(const std::ssortingng&) est remplacé par foo(const char*) , seul foo("xyz"); Les invocations continueront sans problème de fonctionner, mais il y a très peu de raisons vaguement plausibles (le code C pourrait donc l'appeler aussi? - mais il serait quand même un peu fou de ne pas continuer à fournir un foo(const std::ssortingng&) surcharge pour le code client existant, afin qu'il puisse être implémenté dans C? - peut-être; suppression de la dépendance à l'en-tête - non pertinent pour les ressources informatiques modernes).

std :: cout << "Bonjour le monde!" << std :: endl; // Option 1

std :: cout << "Hello World!" s << std :: endl; // Option 2

Le premier operator<<(std::ostream&, const char*) , en accédant directement aux données littérales de chaîne constante, le seul inconvénient étant que le streaming peut devoir rechercher le NUL final. "option 2" correspond à une surcharge const reference et implique la construction d'un temporaire, bien que les compilateurs soient en mesure de l'optimiser afin de ne pas le faire inutilement souvent, ou même de créer efficacement l'object ssortingng au moment de la compilation (ce qui être pratique pour les chaînes suffisamment courtes pour utiliser une approche SSO (Short Ssortingng Optimization) intégrée à l'object. S'ils ne font pas déjà de telles optimisations, le bénéfice potentiel et donc la pression / le désir de le faire vont probablement augmenter.

Premièrement, je crois que la réponse est basée sur les opinions!

Pour votre exemple 1, vous avez déjà mentionné tous les arguments importants pour utiliser le nouveau littéral. Et oui, je m’attends à ce que le résultat soit le même et je ne vois donc pas le besoin de dire que je veux un std :: ssortingng dans la définition.

Un argument peut être qu’un constructeur est défini explicit et qu’une conversion de type automatique n’aura pas lieu. A cette condition, un littéral est utile.

Mais c’est une question de goût je pense!

Pour votre exemple 2, j’ai tendance à utiliser la “vieille” version c-ssortingng car la génération d’un object std :: ssortingng entraîne une surcharge. Donner un pointeur à la chaîne pour cout est bien défini et je ne vois pas de cas d’utilisation où je pourrais avoir un avantage.

Mon conseil personnel est donc (chaque jour, de nouvelles informations sont disponibles :-)) d’utiliser c-ssortingng si cela correspond exactement à mes besoins. Cela signifie que: La chaîne est constante et ne sera jamais copiée ni modifiée et ne sera utilisée que “telle quelle”. Donc, un std :: ssortingng n’aura tout simplement aucun avantage.

Et utiliser ‘s’-literal est utilisé lorsque j’ai besoin de le définir comme étant un std :: ssortingng.

En bref: je n’utilise pas std :: ssortingng si je n’ai pas besoin des fonctionnalités supplémentaires offertes par std :: ssortingng par rapport à un ancien c-ssortingng. Pour moi, le point n’est pas d’utiliser le s-littéral mais l’utilisation de std :: ssortingng vs c-ssortingngs en général.

Seulement comme remarque: je dois beaucoup programmer sur de très petits appareils intégrés, en particulier sur des AVR 8 bits. L’utilisation de std :: ssortingng entraîne beaucoup de temps système. Si je dois utiliser un conteneur dynamic parce que j’ai besoin des fonctionnalités de ce conteneur, il est très bon d’en avoir un qui est très bien implémenté et testé. Mais si je n’en ai pas besoin, c’est trop coûteux.

Sur une grande cible comme une boîte x86, std :: ssortingng semble être négligeable au lieu de c-ssortingng. Mais avoir un petit appareil en tête vous donne une idée de ce qui se passe réellement aussi sur les grosses machines.

Seulement mes deux cents!

Dans quels cas c’est une bonne (ou mauvaise) idée de construire un object std :: ssortingng non nommé, au lieu d’utiliser simplement un littéral de chaîne de style C?

Ce qui est ou non une bonne idée a tendance à varier avec la situation.

Mon choix est d’ utiliser des littéraux bruts chaque fois qu’ils sont suffisants (chaque fois que je n’ai besoin de rien d’autre que d’un littéral). Si j’ai besoin d’accéder à autre chose qu’à un pointeur sur le premier élément de la chaîne (la longueur de la chaîne, sa longueur, ses iterators ou autre), j’utilise un littéral std :: ssortingng.

Bien entendu, dans tous les cas, la construction d’un object std :: ssortingng peut donner lieu à une allocation de tas, ce qui peut entraîner un rejet. Par conséquent, la sécurité des exceptions doit également être prise en compte.

Euh … Bien que le code puisse effectivement être lancé, cela n’a aucune importance, sauf dans des circonstances très particulières (par exemple, un code intégré s’exécutant à la limite de mémoire du matériel ou à une application / un environnement à haute disponibilité).

En pratique, je n’ai jamais eu de manque de mémoire, auto a = "abdce"s; ou autre code similaire.

En conclusion, ne vous préoccupez pas de la sécurité exceptionnelle des situations de mémoire insuffisante résultant de l’instanciation d’un littéral std :: ssortingng . Si vous rencontrez une situation de mémoire insuffisante, modifiez le code lorsque vous trouvez l’erreur.