web-dev-qa-db-fra.com

Dois-je comparer une chaîne std :: à une "chaîne" ou une "chaîne"?

Considérez cet extrait de code:

bool foo(const std::string& s) {
    return s == "hello"; // comparing against a const char* literal
}

bool bar(const std::string& s) {
    return s == "hello"s; // comparing against a std::string literal
}

À première vue , il semble que comparer avec un const char* Nécessite moins d'instructions d'assemblage1, car l'utilisation d'un littéral de chaîne entraînera une construction sur place du std::string.

( EDIT: Comme indiqué dans les réponses, j'ai oublié le fait que s.compare(const char*) sera effectivement appelé dans foo(), donc bien sûr, aucune construction sur place n'a lieu dans ce cas. Par conséquent, supprimez certaines lignes ci-dessous. )

Cependant, en regardant la référence operator==(const char*, const std::string&):

Toutes les comparaisons sont effectuées via la fonction membre compare().

D'après ma compréhension, cela signifie que nous devrons de toute façon construire un std::string Afin d'effectuer la comparaison, donc je soupçonne que les frais généraux seront les mêmes à la fin (bien que masqués par l'appel à operator==).

  • Laquelle des comparaisons dois-je préférer?
  • Une version a-t-elle des avantages sur l'autre (peut être dans des situations spécifiques)?

1 Je suis conscient que moins d'instructions d'assemblage ne signifie pas nécessairement un code plus rapide, mais je ne veux pas entrer ici dans le micro-benchmarking.

44
andreee

Ni.

Si vous voulez être intelligent, comparez à "string"sv , qui renvoie un std::string_view .


En comparant un littéral comme "string" n'entraîne aucune surcharge d'allocation, il est traité comme une chaîne terminée par null, avec tous les inconvénients concomittants: aucune tolérance pour les nulls incorporés, et les utilisateurs doivent tenir compte du terminateur null.

"string"s fait une allocation, sauf optimisation des petites chaînes ou élision d'allocation . De plus, l'opérateur obtient la longueur du littéral, pas besoin de compter, et il permet des valeurs NULL incorporées.

Et enfin en utilisant "string"sv combine les avantages des deux autres approches, en évitant leurs inconvénients individuels. Aussi un std::string_view est une bête beaucoup plus simple qu'un std::string, surtout si ce dernier utilise SSO comme tous les modernes.


Au moins depuis C++ 14 (qui permettait généralement d'éliminer les allocations), les compilateurs pouvaient en théorie optimiser toutes les options par rapport à la dernière, à condition de disposer d'informations suffisantes (généralement disponibles pour l'exemple) et d'efforts, sous la comme si la règle . Mais nous n'en sommes pas encore là.

66
Deduplicator

Non, compare() ne nécessite pas la construction d'un std::string pour const char* opérandes.

Vous utilisez surcharge # 4 ici .

La comparaison avec le littéral de chaîne est la version "gratuite" que vous recherchez. Instanciation d'un std::string ici, c'est complètement inutile.

D'après ma compréhension, cela signifie que nous devrons de toute façon construire un std::string Afin d'effectuer la comparaison, donc je soupçonne que les frais généraux seront les mêmes à la fin (bien que masqués par l'appel à operator==).

C'est là que ce raisonnement tourne mal. std::compare n'a pas besoin d'allouer son opérande en tant que chaîne à terminaison nulle de style C pour fonctionner. Selon l'une des surcharges:

int compare( const CharT* s ) const; // (4)

4) Compare cette chaîne à la séquence de caractères terminée par un caractère nul commençant par le caractère pointé par s avec la longueur Traits::length(s).

Bien que l'allocation ou non soit un détail d'implémentation, il ne semble pas raisonnable qu'une comparaison de séquence le fasse.