Pourquoi string :: compare renvoie-t-il un int?

102

Pourquoi string::comparerenvoie un intau lieu d'un type plus petit comme shortou char? Je crois comprendre que cette méthode ne renvoie que -1, 0 ou 1.

Deuxième partie, si je devais concevoir une méthode de comparaison qui compare deux objets de type Fooet que je ne voulais renvoyer que -1, 0 ou 1, est-ce que l'utilisation shortou chargénéralement serait une bonne idée?

EDIT: J'ai été corrigé, string::comparene renvoie pas -1, 0 ou 1, il renvoie en fait une valeur> 0, <0 ou 0. Merci de me garder en ligne les gars.

Il semble que la réponse soit approximativement, il n'y a aucune raison de renvoyer un type plus petit que intparce que les valeurs de retour sont des "rvalues" et que ces "rvalues" ne bénéficient pas d'être plus petites que le type int (4 octets). En outre, de nombreuses personnes ont souligné que les registres de la plupart des systèmes seront probablement de inttoute façon de toute façon, puisque ces registres vont être remplis que vous leur donniez une valeur de 1, 2 ou 4 octets, il n'y a pas de réel avantage à renvoyer un plus petite valeur.

EDIT 2: En fait, il semble qu'il puisse y avoir une surcharge de traitement supplémentaire lors de l'utilisation de types de données plus petits tels que l'alignement, le masquage, etc. Le consensus général est que les plus petits types de données existent pour économiser la mémoire lorsque vous travaillez avec beaucoup de données, comme dans le cas d'un tableau.

J'ai appris quelque chose aujourd'hui, merci encore les gars!

Cody Smith
la source
Je pense que ce serait mieux s'il y avait un type plus spécifique qui pourrait être utilisé pour cela. Celui qui ne contient que -1, 0 et 1 dans le style d'Ada95.
Sachin Kainth
23
La documentation pour string::compare()vous lien vers indique clairement que la valeur de retour est <0, 0 et> 0 -pas- -1, 0 et 1.
Captain Obvlious
6
Quel serait l'avantage d'utiliser shortou à la charplace de int? La plupart des architectures vont stocker la valeur de retour d'une fonction dans un registre, et un intrentrera dans un registre aussi bien qu'un shortou char. Et utiliser charpour les types numériques est toujours une mauvaise idée, en particulier lorsque vous devez garantir que les valeurs signées sont gérées correctement.
Cody Gray
7
Capitaine Obvlious, votre nom et commentaire ... Inestimable.
Cody Smith
2
L'utilisation charserait une mauvaise idée, car la vérification du code pour la valeur de retour si elle est inférieure à zéro échouera sur les plates-formes où charn'est pas signé.
milleniumbug

Réponses:

113

Premièrement, la spécification est qu'elle renverra une valeur inférieure, égale ou supérieure à 0, pas nécessairement -1ou 1. Deuxièmement, les valeurs de retour sont des rvalues, soumises à une promotion intégrale, il est donc inutile de renvoyer quelque chose de plus petit.

En C ++ (comme en C), chaque expression est une rvalue ou une lvalue. Historiquement, les termes font référence au fait que les valeurs l apparaissent à gauche d'une affectation, alors que les valeurs r ne peuvent apparaître qu'à droite. Aujourd'hui, une simple approximation pour les types non-classe est qu'une lvalue a une adresse en mémoire, pas une rvalue. Ainsi, vous ne pouvez pas prendre l'adresse d'une rvalue, et les qualificatifs cv (qui condition "accès") ne s'appliquent pas. En termes C ++, une rvalue qui n'a pas de type de classe est une valeur pure, pas un objet. La valeur de retour d'une fonction est une rvalue, sauf si elle a un type de référence. (Les types non-classe qui tiennent dans un registre seront presque toujours retournés dans un registre, par exemple, plutôt qu'en mémoire.)

Pour les types de classe, les problèmes sont un peu plus complexes, en raison du fait que vous pouvez appeler des fonctions membres sur une rvalue. Cela signifie que rvalues ​​doit en fait avoir des adresses, pour le this pointeur, et peut être qualifié cv, puisque la qualification cv joue un rôle dans la résolution de surcharge. Enfin, C ++ 11 introduit plusieurs nouvelles distinctions, afin de prendre en charge les références rvalue; ceux-ci sont également principalement applicables aux types de classe.

La promotion intégrale fait référence au fait que lorsque des types intégraux plus petits que un intsont utilisés comme valeurs r dans une expression, dans la plupart des contextes, ils seront promus vers int. Donc, même si j'ai une variable déclarée short a, b;, dans l'expression a + b, les deux aet bsont promus intavant que l'ajout ne se produise. De même, si j'écris a < 0, la comparaison se fait sur la valeur de a, convertie en un int. Dans la pratique, il y a très peu de cas où cela fait une différence, au moins sur les compléments à 2 machines où l'arithmétique entière s'enroule (c'est-à-dire tous les exotiques sauf très peu, aujourd'hui - je pense que les mainframes Unisys sont les seules exceptions qui restent). Pourtant, même sur les machines les plus courantes:

short a = 1;
std::cout << sizeof( a ) << std::endl;
std::cout << sizeof( a + 0 ) << std::endl;

devrait donner des résultats différents: le premier est l'équivalent de sizeof( short ), le second sizeof( int )(en raison de la promotion intégrale).

Ces deux problèmes sont formellement orthogonaux; rvalues ​​et lvalues ​​n'ont rien à voir avec la promotion intégrale. Sauf que ... la promotion intégrale ne s'applique qu'aux valeurs r, et la plupart (mais pas tous) des cas où vous utiliseriez une rvalue entraîneront une promotion intégrale. Pour cette raison, il n'y a vraiment aucune raison de renvoyer une valeur numérique dans quelque chose de plus petit que int. Il y a même une très bonne raison de ne pas le renvoyer comme type de caractère. Les opérateurs surchargés, comme <<, se comportent souvent différemment pour les types de caractères, vous souhaitez donc uniquement renvoyer des caractères sous forme de types de caractères. (Vous pouvez comparer la différence:

char f() { return 'a'; }
std::cout << f() << std::endl;      //  displays "a"
std::cout << f() + 0 << std::endl;  //  displays "97" on my machine

La différence est que dans le second cas, l'ajout a provoqué une promotion intégrale, ce qui entraîne une surcharge différente de <<à choisir.

James Kanze
la source
46
Ce serait bien si vous pouviez en expliquer davantage return values are rvalues, subject to integral promotiondans votre réponse.
Alvin Wong
"les valeurs de retour sont des valeurs r ... il est donc inutile de renvoyer quoi que ce soit de plus petit" LIKE IT
masoud
1
@AlvinWong: Voir les réponses à Pourquoi les caractères C sont-ils des entiers au lieu de caractères? pour plus d'informations générales.
Jesse Good
J'aimerais pouvoir attribuer +1 à ce sujet à nouveau, après la superbe explication que votre modification a ajoutée.
Cody Gray
Et si c'était le cas signed char? Se comporterait-il de la même manière qu'un signe charou serait-il d'un type différent?
user541686
41

Il est intentionnel qu'il ne renvoie pas -1, 0 ou 1.

Cela permet (notez que ce n'est pas pour les chaînes, mais cela s'applique également aux chaînes)

int compare(int *a, int *b)
{
   return *a - *b;
}

ce qui est beaucoup moins encombrant que:

int compare(int *a, int *b)
{
   if (*a == *b) return 0;
   if (*a > *b) return 1;
   return -1;
}

c'est ce que vous auriez à faire [ou quelque chose du genre] si vous devez renvoyer -1, 0 ou 1.

Et cela fonctionne aussi pour les types plus complexes:

class Date
{
    int year;
    int month;
    int day;
}

int compare(const Date &a, const Date &b)
{
   if (a.year != b.year) return a.year - b.year;
   if (a.month != b.month) return a.month - b.month;
   return a.day - b.day;
}

Dans le cas des chaînes, nous pouvons faire ceci:

int compare(const std::string& a, const std::string& b)
{
   int len = min(a.length(), b.length());

   for(int i = 0; i < len; i++)
   {
      if (a[i] != b[i]) return a[i] - b[i];
   }
   // We only get here if the string is equal all the way to one of them
   // ends. If the length isn't equal, "longest" wins. 
   return a.length() - b.length();
}
Mats Petersson
la source
8
Votre première comparefonction a des problèmes de débordement qui (heureusement) ne s'appliquent pas également si elle prend char*et charest inférieure à int. Par exemple, si *aest MAX_INTet *best -1alors *a - *best UB, mais si l'implémentation choisit de définir son comportement alors le résultat est presque certainement négatif.
Steve Jessop
1
Problème avec votre dernier exemple: length()renvoie a size_t, qui peut être plus grand que int
F'x
Ouais, cela peut être un problème si vos chaînes font plus de 2 Go de long. J'ai fait de longues chaînes de 1 Go comme cas de test pour stocker des choses dans un fifo une fois. Mais bien sûr, quelqu'un qui a affaire à une chaîne contenant un MPEG encodé en Base64 ou quelque chose du genre pourrait bien rencontrer ce problème ...
Mats Petersson
@MatsPetersson, c'est plus un problème fondamental, car la question est «pourquoi renvoie-t-il un int?»
F'x
Eh bien, je suis sûr que c'est hystérique - je veux dire des raisons historiques - et probablement pour qu'il soit compatible avec strcmp / memcmp et d'autres opérations de type de comparaison.
Mats Petersson
25

int est généralement (c'est-à-dire sur la plupart des matériels modernes) un entier de la même taille que le bus système et / ou les registres cpu, ce qu'on appelle le mot machine. Par conséquent, int est généralement transmis plus rapidement que les types plus petits, car il ne nécessite pas d'alignement, de masquage et d'autres opérations.

Les types plus petits existent principalement pour permettre l'optimisation de l'utilisation de la RAM pour les tableaux et les structures. Dans la plupart des cas, ils échangent quelques cycles CPU (sous forme d'opérations d'alignement) pour une meilleure utilisation de la RAM.

À moins que vous n'ayez besoin de forcer votre valeur de retour à être un nombre signé ou non signé d'une taille de centain (char, short…), il vaut mieux utiliser int, c'est pourquoi la bibliothèque standard le fait.

Tobie
la source
Excellent moyen d'expliquer le côté matériel des choses d'une manière qui a du sens.
Ogre Psalm33
10

C'est un C-isme.

Lorsque C exigeait des comparefonctions de type -type, elles renvoyaient toujours un fichier int. C ++ vient de faire avancer cela (malheureusement).

Cependant, renvoyer un intest probablement le moyen le plus rapide, car il s'agit généralement de la taille des registres du système utilisé. (Délibérément vague.)

Alex Chamberlain
la source
1
En fait, shortet charpeut imposer des pénalités de performance, par exemple 255+7a une valeur différente pour a charet an intdonc une implémentation correcte ne peut pas nécessairement simplement stocker un charendroit où un intpeut aller sans se soucier de remettre sa sémantique. Les compilateurs n'optimiseront pas nécessairement l'inefficacité que cela impose.
Jack Aidley
10

La méthode ne renvoie pas réellement un entier dans l'ensemble { -1, 0, 1 }; il peut en fait s'agir de n'importe quelle valeur intégrale.

Pourquoi? La principale raison à laquelle je peux penser est que intc'est censé être la valeur «grandeur nature» de l'architecture; les opérations sur des valeurs de cette taille sont généralement au moins aussi rapides (et dans de nombreux cas plus rapides) que les opérations sur des valeurs plus petites ou plus grandes. Il s'agit donc de laisser une mise en œuvre suffisamment lâche pour utiliser ce qui est le plus rapide.

Jon
la source
4

si je devais concevoir une méthode de comparaison qui compare deux objets de type Foo et que je ne voulais renvoyer que -1, 0 ou 1, est-ce que l'utilisation de short ou char serait généralement une bonne idée?

Ce serait une bonne idée. Une meilleure façon serait de retourner un bool (si seulement vous voulez comparer si égal), ou enum (pour plus d'informations):

enum class MyResult
{
  EQUAL,
  LESS,
  GREATER
};

MyResult AreEqual( const Foo &foo1, const Foo & foo2 )
{
  // calculate and return result
}
BЈовић
la source
3
"Ce serait une bonne idée". Avez-vous une justification à cela?
jrok
4

Supposons que certaines personnes changent un code de C à C ++. Ils ont décidé de remplacer strcmpà string::compare.

Depuis les strcmpretours int, il est plus facile de string::comparerevenir int, en cadeau.

Masoud
la source
2

Probablement pour le faire fonctionner plus comme strcmpqui a également cet ensemble de valeurs de retour . Si vous vouliez porter du code, il serait probablement plus intuitif d'avoir des remplacements aussi proches que possible.

En outre, la valeur de retour n'est pas simplement -1, 0ou 1mais <0, 0ou >0.

De plus, comme cela a été mentionné, puisque le retour fait l'objet d' une promotion intégrale, il n'est pas logique de le réduire.

Shafik Yaghmour
la source
-1

car une valeur de retour booléenne ne peut être que deux valeurs possibles (vrai, faux) et une fonction de comparaison peut renvoyer trois valeurs possibles (inférieure à, égale, supérieure à).

Mettre à jour

Bien qu'il soit certainement possible de renvoyer un short signé, si vous vouliez vraiment implémenter votre propre fonction de comparaison, vous pouvez renvoyer une valeur nibble ou struct avec deux booléens.

MDMoore313
la source
7
Nulle part dans la question ne dit quoi que ce soit sur le retour d'un type booléen. En fait, il propose spécifiquement shortet charcomme alternatives à int.
Cody Gray