Kiedy używać std :: size_t?

201

Zastanawiam się, czy powinienem używać std::size_tpętli i innych rzeczy zamiast int? Na przykład:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

Ogólnie, jaka jest najlepsza praktyka dotycząca tego, kiedy stosować std::size_t?

nhaa123
źródło

Odpowiedzi:

185

Dobrą zasadą jest, aby wszystko, co trzeba porównać w pętli z czymś, co jest naturalnie std::size_tsamym sobą.

std::size_tjest rodzajem dowolnego sizeofwyrażenia i gwarantuje, że będzie w stanie wyrazić maksymalny rozmiar dowolnego obiektu (w tym dowolnej tablicy) w C ++. Przez rozszerzenie jest również zagwarantowane, że jest wystarczająco duży dla dowolnego indeksu tablicy, więc jest to naturalny typ dla pętli po indeksie nad tablicą.

Jeśli liczysz tylko liczbę, może być bardziej naturalne użycie typu zmiennej, która przechowuje tę liczbę, intlub unsigned int( lub wystarczająco duży), ponieważ powinny one mieć naturalny rozmiar dla maszyny.

CB Bailey
źródło
40
Warto wspomnieć, że nie stosując size_tkiedy należy może prowadzić do błędów bezpieczeństwa .
BlueRaja - Danny Pflughoeft
5
Int jest nie tylko „naturalny”, ale także mieszanie podpisanego i niepodpisanego typu może prowadzić do błędów bezpieczeństwa. Niepodpisane indeksy są trudnym zadaniem i dobrym powodem do użycia niestandardowej klasy wektorowej.
Jo So
2
@JoSo Istnieje również ssize_tdla podpisanych wartości.
EntangledLoops
70

size_tjest typem wyniku sizeofoperatora.

Użyj size_tdla zmiennych, które modelują rozmiar lub indeks w tablicy. size_tprzekazuje semantykę: od razu wiesz, że reprezentuje rozmiar w bajtach lub indeksie, a nie tylko inną liczbę całkowitą.

Ponadto użycie size_tdo reprezentowania rozmiaru w bajtach pomaga uczynić kod przenośnym.

Grzegorz Pakosz
źródło
32

size_tTypu służy do określenia rozmiaru czegoś więc to naturalne, aby go użyć, na przykład, coraz długość łańcucha, a następnie przetwarza każdy znak:

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

Ci nie muszą zwracać uwagę na warunki brzegowe oczywiście, ponieważ jest to typ unsigned. Granica na końcu górnej zwykle nie jest to ważne, ponieważ maksymalna to zazwyczaj duże (choć jest możliwe, aby się tam dostać). Większość ludzi po prostu używa inttego typu rzeczy, ponieważ rzadko mają struktury lub tablice, które stają się wystarczająco duże, aby przekroczyć ich możliwości int.

Ale uważaj na takie rzeczy jak:

for (size_t i = strlen (str) - 1; i >= 0; i--)

co spowoduje nieskończoną pętlę z powodu zawijania się niepodpisanych wartości (chociaż widziałem, że kompilatory ostrzegają przed tym). Można to również złagodzić poprzez (nieco trudniejsze do zrozumienia, ale przynajmniej odporne na problemy z owijaniem):

for (size_t i = strlen (str); i-- > 0; )

Przesunięcie dekrementacji na efekt uboczny warunku kontynuacji po sprawdzeniu powoduje sprawdzenie kontynuacji wartości przed dekrementacją, ale nadal korzysta ze zmniejszonej wartości w pętli (dlatego pętla działa len .. 1raczej niż len-1 .. 0).

paxdiablo
źródło
14
Nawiasem mówiąc, złą praktyką jest wzywanie strlenkażdej iteracji pętli. :) Możesz zrobić coś takiego:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil
1
Nawet jeśli byłby to typ podpisany, musisz uważać na warunki brzegowe, być może nawet bardziej, ponieważ podpisane przepełnienie liczb całkowitych jest nieokreślonym zachowaniem.
Adrian McCarthy
2
Prawidłowe odliczanie można wykonać w następujący (niesławny) sposób:for (size_t i = strlen (str); i --> 0;)
Jo So
1
@JoSo, to całkiem fajna sztuczka, choć nie jestem pewien, czy podoba mi się wprowadzenie -->operatora „idzie do” (patrz stackoverflow.com/questions/1642028/... ). Włączyłem twoją sugestię do odpowiedzi.
paxdiablo
Czy możesz zrobić prosty if (i == 0) break;na końcu pętli for (np for (size_t i = strlen(str) - 1; ; --i). (Ale bardziej lubię twój, ale zastanawiam się, czy to też by zadziałało).
RastaJedi
13

Z definicji size_tjest wynikiem sizeofoperatora. size_tzostał stworzony w celu odniesienia do rozmiarów.

Liczba przypadków, w których coś robisz (w twoim przykładzie 10), nie dotyczy rozmiarów, więc po co używać size_t? intlub unsigned intpowinien być w porządku.

Oczywiście istotne jest również to, co robisz iw pętli. Jeśli przekażesz go do funkcji, która bierze unsigned intna przykład pick unsigned int.

W każdym razie zalecam unikanie konwersji typu niejawnego. Wyraźnie wszystkie konwersje typów.

Daniel Daranas
źródło
10

size_tjest bardzo czytelnym sposobem na określenie wymiaru rozmiaru elementu - długości łańcucha, ilości bajtów, które zajmuje wskaźnik itp. Jest także przenośny na różnych platformach - przekonasz się, że 64-bitowy i 32-bitowy zachowują się ładnie dzięki funkcjom systemowym i size_t- coś, co unsigned intmoże nie zrobić (np. kiedy należy użyćunsigned long

Ofir
źródło
9

krótka odpowiedź:

prawie nigdy

długa odpowiedź:

Ilekroć musisz mieć wektor char większy niż 2 GB w systemie 32-bitowym. W każdym innym przypadku użycie podpisanego typu jest znacznie bezpieczniejsze niż użycie niepodpisanego typu.

przykład:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

Podpisany odpowiednik size_tto ptrdiff_tnie int. Ale intw większości przypadków używanie jest znacznie lepsze niż size_t. ptrdiff_tjest longw systemach 32 i 64-bitowych.

Oznacza to, że zawsze musisz konwertować do iz size_t za każdym razem, gdy wchodzisz w interakcje ze std :: container, co nie jest zbyt piękne. Ale podczas trwającej konferencji natywnej autorzy c ++ wspomnieli, że zaprojektowanie std :: vector z niepodpisanym size_t było błędem.

Jeśli Twój kompilator wyświetla ostrzeżenia o niejawnych konwersjach z ptrdiff_t na size_t, możesz to wyraźnie wyrazić za pomocą składni konstruktora:

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

jeśli chcesz po prostu iterować kolekcję, bez sprawdzania granic, użyj zakresu opartego na:

for(const auto& d : data) {
    [...]
}

tutaj kilka słów od Bjarne Stroustrup (autor C ++) na temat przejścia na język ojczysty

Dla niektórych osób ten błąd projektu podpisany / niepodpisany w STL jest wystarczającym powodem, aby nie używać std :: vector, ale zamiast własnej implementacji.

Arne
źródło
1
Rozumiem, skąd pochodzą, ale nadal myślę, że pisanie jest dziwne for(int i = 0; i < get_size_of_stuff(); i++). Oczywiście, możesz nie chcieć robić wielu surowych pętli, ale - daj spokój, też ich używasz.
einpoklum
Jedynym powodem, dla którego używam pętli raw, jest to, że biblioteka algorytmów c ++ jest źle zaprojektowana. Istnieją języki, takie jak Scala, które mają znacznie lepszą i bardziej rozwiniętą bibliotekę do obsługi zbiorów. Wtedy przypadek użycia surowych pętli jest prawie całkowicie wyeliminowany. Istnieją również podejścia do ulepszenia c ++ za pomocą nowego i lepszego STL, ale wątpię, aby stało się to w ciągu następnej dekady.
Arne,
1
Dostaję to bez znaku i = 0; twierdzić (i-1, MAX_INT); ale nie rozumiem, dlaczego mówisz „jeśli już miałem niedomiar, staje się to prawdą”, ponieważ zachowanie arytmetyki na niepodpisanych liczbach wewnętrznych jest zawsze zdefiniowane, tj. wynikiem jest moduł wynikowy wielkości największej reprezentatywnej liczby całkowitej. Więc jeśli i == 0, to i-- staje się MAX_INT, a następnie i ++ staje się ponownie 0.
mabraham
@mabraham Patrzyłem uważnie i masz rację, mój kod nie najlepiej pokazuje problem. Zwykle jest to x + 1 < yrównoważne x < y - 1, ale nie ma liczb całkowitych niebędących wypisanymi. To może z łatwością wprowadzić błędy, gdy rzeczy zostaną przekształcone, co do których zakłada się, że są równoważne.
Arne,
8

Użyj std :: size_t do indeksowania / liczenia tablic typu C.

W przypadku kontenerów STL będziesz mieć (na przykład) vector<int>::size_type, które powinny być używane do indeksowania i zliczania elementów wektorowych.

W praktyce zazwyczaj są to ints bez znaku, ale nie jest to gwarantowane, szczególnie przy użyciu niestandardowych alokatorów.

Peter Alexander
źródło
2
Z gcc na Linuksie std::size_tjest zwykle unsigned long(8 bajtów w systemach 64-bitowych), a nie unisgned int(4 bajty).
rafak
5
Tablice w stylu C nie są jednak indeksowane size_t, ponieważ indeksy mogą być ujemne. Można jednak użyć size_twłasnej instancji takiej tablicy, jeśli nie chce się przyjmować wartości ujemnej.
Johannes Schaub - litb
Czy porównania na U64 są tak szybkie, jak porównania na U32? Określiłem surowe kary za wydajność za używanie U8 i U16 jako wartowników pętli, ale nie wiem, czy Intel zdążył działać razem w 64s.
Crashworks
2
Ponieważ indeksowanie tablic w stylu C jest równoważne z użyciem operatora +na wskaźnikach, wydaje się, że ptrdiff_tjest to jeden z indeksów.
Pavel Minaev
8
Jeśli chodzi o vector<T>::size_type(i to samo dla wszystkich innych pojemników), to jest raczej raczej bezużyteczne, ponieważ skutecznie gwarantuje się, że jest size_t- jest napisane na maszynie Allocator::size_type, a ograniczenia dotyczące kontenerów patrz w szczególności 20.1.5 / 4 - w szczególności size_typemuszą być size_ti difference_typemusi być ptrdiff_t. Oczywiście wartość domyślna std::allocator<T>spełnia te wymagania. Więc po prostu użyj krótszego size_ti nie zawracaj sobie głowy resztą partii :)
Pavel Minaev
7

Wkrótce większość komputerów będzie miała architekturę 64-bitową z 64-bitowym systemem operacyjnym: es z programami działającymi na kontenerach miliardów elementów. Następnie należy użyć size_tzamiast intjako indeksu pętli, w przeciwnym razie indeks będzie zawijał się na elemencie 2 ^ 32: th, zarówno w systemach 32-, jak i 64-bitowych.

Przygotuj się na przyszłość!

Nordlöw
źródło
Twój argument dotyczy tylko tego, co trzeba, long inta nie int. Jeśli size_tma to znaczenie w 64-bitowym systemie operacyjnym, było tak samo istotne w 32-bitowym systemie operacyjnym.
einpoklum
4

Korzystając z size_t, zwróć uwagę na następujące wyrażenie

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

Otrzymasz false w wyrażeniu if bez względu na to, jaką wartość masz dla x. Uświadomienie sobie tego zajęło mi kilka dni (kod jest tak prosty, że nie przeprowadziłem testu jednostkowego), chociaż ustalenie źródła problemu zajęło tylko kilka minut. Nie jestem pewien, czy lepiej jest wykonać rzut lub użyć zera.

if ((int)(i-x) > -1 or (i-x) >= 0)

Oba sposoby powinny działać. Oto mój testowy przebieg

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

Wyjście: i-7 = 18446744073709551614 (int) (i-7) = - 2

Chciałbym komentarze innych.

Kemin Zhou
źródło
2
pamiętaj, że (int)(i - 7)jest to niedomiar, który jest rzutowany intpóźniej, ale int(i) - 7nie jest niedomiar, ponieważ najpierw konwertujesz ina int, a następnie odejmujesz 7. Dodatkowo twój przykład był mylący.
hochl
Chodzi mi o to, że int jest zwykle bezpieczniejszy, gdy robisz odejmowanie.
Kemin Zhou,
4

size_t jest zwracany przez różne biblioteki, aby wskazać, że rozmiar tego kontenera jest różny od zera. Używasz go, gdy wrócisz: 0

Jednak w powyższym przykładzie zapętlenie na size_t jest potencjalnym błędem. Rozważ następujące:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

użycie liczb całkowitych bez znaku może potencjalnie powodować tego rodzaju subtelne problemy. Dlatego imho wolę używać size_t tylko wtedy, gdy wchodzę w interakcje z kontenerami / typami, które tego wymagają.

ascotan
źródło
Wydaje się, że każdy używa size_t w pętli, nie zawracając sobie głowy tym błędem, i nauczyłem się tego na
własnej skórze
-2

size_tjest typem bez znaku, który może przechowywać maksymalną wartość całkowitą dla twojej architektury, więc jest chroniony przed przepełnieniem liczb całkowitych z powodu znaku (podpisany int 0x7FFFFFFFzwiększony o 1 da -1) lub krótki rozmiar (niepodpisany krótki int 0xFFFF zwiększony o 1 da ci 0).

Jest używany głównie w indeksowaniu tablic / pętlach / arytmetyki adresów i tak dalej. Funkcje takie jak memset()i podobne akceptują size_ttylko, ponieważ teoretycznie możesz mieć blok pamięci wielkości 2^32-1(na platformie 32-bitowej).

Dla takich prostych pętli nie zawracaj sobie głowy i używaj tylko int.

Wizzard
źródło
-3

size_t to typ całki bez znaku, który może reprezentować największą liczbę całkowitą w twoim systemie. Używaj go tylko wtedy, gdy potrzebujesz bardzo dużych tablic, macierzy itp.

Niektóre funkcje zwracają rozmiar_t, a twój kompilator ostrzega cię, jeśli spróbujesz dokonać porównań.

Unikaj tego, używając odpowiedniego podpisanego / niepodpisanego typu danych lub po prostu typecast dla szybkiego włamania.

Król Małp
źródło
4
Używaj go tylko wtedy, gdy chcesz uniknąć błędów i dziur bezpieczeństwa.
Craig McQueen,
2
Może nie być w stanie reprezentować największej liczby całkowitej w twoim systemie.
Adrian McCarthy
-4

size_t jest bez znaku int. więc kiedy tylko chcesz int bez znaku, możesz go użyć.

Używam go, gdy chcę określić rozmiar tablicy, przeciwdziałać ...

void * operator new (size_t size); is a good use of it.
Ashish
źródło
10
W rzeczywistości niekoniecznie jest to to samo, co unsigned int. To jest niepodpisany, ale może być większa (lub mniejsza, choć myślę, że nie wiem o wszelkich platformach gdzie jest to prawdą) niż wew.
Todd Gamblin,
Na przykład na komputerze 64-bitowym size_tmoże być 64-bitowa liczba całkowita bez znaku, podczas gdy na maszynie 32-bitowej jest to tylko 32-bitowa liczba całkowita bez znaku.
HerpDerpington