Dlaczego string :: compare zwraca int?

102

Dlaczego string::comparezwraca intzamiast mniejszego typu, jak shortlub char? Rozumiem, że ta metoda zwraca tylko -1, 0 lub 1.

Druga część, gdybym miał zaprojektować metodę porównania, która porównuje dwa obiekty typu Fooi chciałbym zwrócić tylko -1, 0 lub 1, czy użycie shortlub charogólnie byłoby dobrym pomysłem?

EDYCJA: Zostałem poprawiony, string::comparenie zwraca -1, 0 ani 1, w rzeczywistości zwraca wartość> 0, <0 lub 0. Dzięki za trzymanie mnie w kolejce.

Wygląda na to, że odpowiedź jest mniej więcej taka, że ​​nie ma powodu, aby zwracać typ mniejszy niż intdlatego, że zwracane wartości to „rvalues”, a te „rvalues” nie korzystają z tego, że są mniejsze niż typ int (4 bajty). Wiele osób wskazywało również, że rejestry większości systemów prawdopodobnie i tak będą miały rozmiar int, ponieważ te rejestry będą wypełnione bez względu na to, czy dasz im wartość 1, 2 czy 4 bajty, nie ma realnej korzyści z zwracania wartości mniejsza wartość.

EDYCJA 2: W rzeczywistości wygląda na to, że przy użyciu mniejszych typów danych, takich jak wyrównanie, maskowanie itp., Może wystąpić dodatkowe obciążenie związane z przetwarzaniem. Ogólny konsensus jest taki, że mniejsze typy danych istnieją, aby oszczędzać pamięć podczas pracy z dużą ilością danych, jak w przypadek tablicy.

Nauczyłem się czegoś dzisiaj, jeszcze raz dziękuję chłopaki!

Cody Smith
źródło
Myślę, że byłoby lepiej, gdyby istniał bardziej konkretny typ, którego można by do tego użyć. Jeden, który zawiera tylko -1, 0 i 1 w stylu Ada95.
Sachin Kainth
23
Dokumentacja, do string::compare()której
odsyłasz,
6
Jaka byłaby korzyść z używania shortlub charzamiast int? Większość architektur będzie przechowywać zwracaną wartość funkcji w rejestrze, a intznak będzie pasował do rejestru tak samo dobrze, jak shortlub char. A używanie chardla typów liczbowych jest zawsze złym pomysłem, zwłaszcza gdy trzeba zagwarantować, że podpisane wartości są obsługiwane poprawnie.
Cody Gray
7
Kapitanie Obvlious, twoje imię i komentarz ... Po prostu bezcenne.
Cody Smith
2
Używanie charbyłoby złym pomysłem, ponieważ sprawdzanie kodu pod kątem wartości zwracanej, jeśli jest mniejsza od zera, zakończy się niepowodzeniem na platformach, na których charjest bez znaku.
milleniumbug

Odpowiedzi:

113

Po pierwsze, specyfikacja jest taka, że ​​zwróci wartość mniejszą niż, równą lub większą niż 0, niekoniecznie -1lub 1. Po drugie, zwracane wartości to rvalues ​​podlegające integralnej promocji, więc nie ma sensu zwracać niczego mniejszego.

W C ++ (podobnie jak w C) każde wyrażenie jest albo wartością r, albo lwartością. Historycznie, terminy te odnoszą się do faktu, że lwartości pojawiają się po lewej stronie przydziału, podczas gdy jako rwartości mogą pojawiać się tylko po prawej stronie. Obecnie prostym przybliżeniem dla typów nieklasowych jest to, że l-wartość ma adres w pamięci, a r-wartość nie. Dlatego nie możesz wziąć adresu wartości r, a kwalifikatory cv (których warunek „dostęp”) nie mają zastosowania. W terminologii C ++ rwartość, która nie ma typu klasy, jest czystą wartością, a nie obiektem. Wartość zwracana funkcji jest wartością r, chyba że ma typ referencyjny. (Typy nieklasowe, które mieszczą się w rejestrze, prawie zawsze będą zwracane na przykład w rejestrze, a nie w pamięci).

W przypadku typów klas problemy są nieco bardziej złożone, ze względu na fakt, że można wywoływać funkcje składowe na wartości r. Oznacza to, że wartości r muszą w rzeczywistości mieć adresy dla this wskaźnika i mogą być kwalifikowane jako cv, ponieważ kwalifikacja cv odgrywa rolę w rozwiązywaniu przeciążeń. Wreszcie, C ++ 11 wprowadza kilka nowych rozróżnień, aby wspierać odwołania do rvalue; te również mają zastosowanie głównie do typów klas.

Promocja integralna odnosi się do faktu, że gdy typy całkowite mniejsze niż an intsą używane jako rwartości w wyrażeniu, w większości kontekstów będą promowane int. Więc nawet jeśli mam zadeklarowaną zmienną short a, b;, w wyrażeniu a + bobie ai bsą promowane intprzed dodaniem. Podobnie, jeśli napiszę a < 0, porównanie odbywa się na wartości a, przekonwertowanej na int. W praktyce jest bardzo niewiele przypadków, w których ma to znaczenie, przynajmniej na maszynach 2 uzupełniających, w których zawija się arytmetykę liczb całkowitych (tj. Wszystkie oprócz bardzo nielicznych egzotyków, dziś - myślę, że jedynymi pozostałymi wyjątkami są komputery mainframe Unisys). Mimo to, nawet na bardziej popularnych komputerach:

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

powinny dawać różne wyniki: pierwszy jest odpowiednikiem sizeof( short ), drugi sizeof( int )(ze względu na integralną promocję).

Te dwie kwestie są formalnie ortogonalne; rvalues ​​i lvalues ​​nie mają nic wspólnego z integralną promocją. Z wyjątkiem ... integralna promocja dotyczy tylko wartości r, a większość (ale nie wszystkie) przypadków, w których użyjesz wartości r, spowoduje integralną promocję. Z tego powodu naprawdę nie ma powodu, aby zwracać wartość liczbową w czymś mniejszym niż int. Jest nawet bardzo dobry powód, aby nie zwracać go jako typu znakowego. Przeciążone operatory, takie jak <<, często zachowują się inaczej w przypadku typów znaków, więc chcesz zwracać tylko znaki jako typy znaków. (Możesz porównać różnicę:

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

Różnica polega na tym, że w drugim przypadku dodanie spowodowało wystąpienie promocji integralnej, co skutkuje <<wybraniem innego przeciążenia .

James Kanze
źródło
46
Byłoby miło, gdybyś mógł wyjaśnić więcej return values are rvalues, subject to integral promotionw swojej odpowiedzi.
Alvin Wong,
"wartości zwracane to rvalues ​​... więc nie ma sensu zwracać niczego mniejszego" LIKE IT
masoud
1
@AlvinWong: Zobacz odpowiedzi na pytanie, dlaczego literały znaków C są ints zamiast chars? aby uzyskać więcej informacji ogólnych.
Jesse Good
Chciałbym móc dać temu jeszcze raz +1 po wspaniałym wyjaśnieniu dodanym przez Twoją zmianę.
Cody Gray
A co jeśli tak było signed char? Czy zachowywałby się tak samo jak podpisany char, czy byłby innego typu?
user541686
41

Jest celowe, że nie zwraca -1, 0 lub 1.

Pozwala (uwaga: nie dotyczy to strun, ale dotyczy to również strun)

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

co jest dużo mniej uciążliwe niż:

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

czyli co musiałbyś zrobić [lub coś w tych liniach], jeśli musisz zwrócić -1, 0 lub 1.

Działa to również w przypadku bardziej złożonych typów:

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;
}

W przypadku stringów możemy to zrobić:

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
źródło
8
Twoja pierwsza comparefunkcja ma problemy z przepełnieniem, które (na szczęście) nie mają jednakowego zastosowania, jeśli zajmuje char*i charjest mniejsze niż int. Na przykład, jeśli *ajest MAX_INTi *bjest, -1to *a - *bjest UB, ale jeśli implementacja zdecyduje się zdefiniować swoje zachowanie, wynik prawie na pewno jest ujemny.
Steve Jessop
1
Problem z ostatnim przykładem: length()zwraca wartość a size_t, która może być większa niż int
F'x
Tak, to może być problem, jeśli twoje struny mają więcej niż 2 GB. Zrobiłem raz łańcuchy o długości 1 GB jako walizkę testową do przechowywania rzeczy w kolejce fifo. Ale na pewno ktoś, kto ma do czynienia z ciągiem znaków zawierającym MPEG zakodowany jako Base64 lub jakimś podobnym, może napotkać ten problem ...
Mats Petersson
@MatsPetersson to bardziej podstawowy problem, ponieważ pytanie brzmi „dlaczego zwraca int?”
F'x
Cóż, jestem pewien, że jest to histeryczne - mam na myśli przyczyny historyczne - i prawdopodobnie dlatego, że jest kompatybilne z strcmp / memcmp i innymi operacjami typu porównania.
Mats Petersson
25

int jest zwykle (co oznacza na większości współczesnych urządzeń) liczbą całkowitą o takim samym rozmiarze jak magistrala systemowa i / lub rejestry procesora, czyli tak zwane słowo maszynowe. Dlatego int jest zwykle przekazywany szybciej niż mniejsze typy, ponieważ nie wymaga wyrównywania, maskowania i innych operacji.

Mniejsze typy istnieją głównie po to, aby umożliwić optymalizację wykorzystania pamięci RAM dla tablic i struktur. W większości przypadków wymieniają kilka cykli procesora (w formie operacji dopasowywania) na lepsze wykorzystanie pamięci RAM.

O ile nie musisz wymuszać, aby zwracana wartość była liczbą ze znakiem lub bez znaku o wielkości centa (znak, krótki…), lepiej jest używać int, dlatego robi to biblioteka standardowa.

Tobia
źródło
Świetny sposób na wyjaśnienie strony sprzętowej w sposób, który ma sens.
Ogre Psalm33
10

To jest C-izm.

Gdy C wymagało comparefunkcji typu -type, zawsze zwracały plik int. C ++ po prostu poszedł naprzód (niestety).

Jednak zwrot intjest prawdopodobnie najszybszym sposobem, ponieważ zazwyczaj jest to rozmiar rejestrów używanego systemu. (Celowo niejasne.)

Alex Chamberlain
źródło
1
W rzeczywistości shorti charmoże nakładać kary za wydajność, np. 255+7Ma inną wartość dla charai, intwięc prawidłowa implementacja nie musi koniecznie po prostu przechowywać a, chargdzie intmoże przejść, nie dbając o przekazanie jego semantyki. Kompilatory niekoniecznie optymalizują wynikającą z tego nieefektywność.
Jack Aidley,
10

Metoda w rzeczywistości nie zwraca liczby całkowitej w zestawie { -1, 0, 1 }; w rzeczywistości może to być dowolna wartość całkowita.

Czemu? Główny powód, jaki przychodzi mi do głowy, jest taki, że intma to być wartość „naturalnej wielkości” architektury; operacje na wartościach tego rozmiaru są zwykle co najmniej tak samo szybkie (aw wielu przypadkach szybsze) niż operacje na mniejszych lub większych wartościach. Jest to więc przypadek, w którym implementacja jest wystarczająco wolna, aby używać tego, co jest najszybsze.

Jon
źródło
4

Gdybym miał zaprojektować metodę porównania, która porównuje dwa obiekty typu Foo i chciałbym zwrócić tylko -1, 0 lub 1, czy użycie short lub char byłoby ogólnie dobrym pomysłem?

Byłby to dobry pomysł. Lepszym sposobem byłoby zwrócenie wartości bool (jeśli tylko chcesz porównać, jeśli równa się) lub wyliczenia (aby uzyskać więcej informacji):

enum class MyResult
{
  EQUAL,
  LESS,
  GREATER
};

MyResult AreEqual( const Foo &foo1, const Foo & foo2 )
{
  // calculate and return result
}
BЈовић
źródło
3
„To byłby dobry pomysł”. Czy masz na to uzasadnienie?
jrok
4

Załóżmy, że niektórzy ludzie zmieniają kod z C na C ++. Postanowili zamienić strcmpna string::compare.

Ponieważ strcmpwraca int, łatwiej jest string::comparezwrócić intjako prezent.

masoud
źródło
2

Prawdopodobnie, aby działało bardziej jak, strcmpktóry również ma ten zestaw wartości zwracanych . Jeśli chciałbyś przenieść kod, prawdopodobnie byłoby bardziej intuicyjne, gdyby zamienniki były tak blisko, jak to tylko możliwe.

Ponadto, wartość zwracana jest nie tylko -1, 0czy 1jednak <0, 0czy >0.

Ponadto, jak wspomniano, skoro zwrot podlega integralnej promocji , nie ma sensu go zmniejszać.

Shafik Yaghmour
źródło
-1

ponieważ logiczna wartość zwracana może być tylko dwiema możliwymi wartościami (prawda, fałsz), a funkcja porównująca może zwracać trzy możliwe wartości (mniejsze niż, równe, większe niż).

Aktualizacja

Chociaż z pewnością możliwe jest zwrócenie podpisanego skrótu, jeśli naprawdę chcesz zaimplementować własną funkcję porównującą, możesz zwrócić wartość nibble lub struct z dwoma parametrami logicznymi.

MDMoore313
źródło
7
Nigdzie w pytaniu nie mówi się nic o zwracaniu typu boolowskiego. W rzeczywistości on specjalnie proponuje shorti charjako alternatywę dla int.
Cody Gray