Ich frage mich nur, ob ich std::size_t
Für Schleifen und andere Dinge anstelle von int
verwenden soll. Zum Beispiel:
#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?
}
}
Was ist im Allgemeinen die beste Vorgehensweise für die Verwendung von std::size_t
?
Eine gute Faustregel gilt für alles, was Sie im Loop-Zustand mit etwas vergleichen müssen, das natürlich selbst ein std::size_t
Ist.
std::size_t
Ist der Typ eines beliebigen sizeof
-Ausdrucks und kann garantiert die maximale Größe eines Objekts (einschließlich eines Arrays) in C++ ausdrücken. Durch die Erweiterung wird auch garantiert, dass es groß genug für jeden Array-Index ist, so dass es ein natürlicher Typ für eine Schleife nach Index über ein Array ist.
Wenn Sie nur bis zu einer Zahl zählen, ist es möglicherweise sinnvoller, entweder den Typ der Variablen zu verwenden, die diese Zahl enthält, oder ein int
oder unsigned int
(Falls groß genug), wie dies sein sollte eine natürliche Größe für die Maschine sein.
size_t
ist der Ergebnistyp des Operators sizeof
.
Verwenden size_t
für Variablen, die Größe oder Index in einem Array modellieren. size_t
vermittelt Semantik: Sie wissen sofort, dass es sich um eine Größe in Bytes oder einen Index handelt und nicht nur um eine andere Ganzzahl.
Auch mit size_t
, um eine Größe in Byte darzustellen, trägt dazu bei, den Code portabel zu machen.
Das size_t
type soll size von etwas angeben, so dass es natürlich ist, es zu verwenden, zum Beispiel die Länge eines Strings abzurufen und dann jedes Zeichen zu verarbeiten:
for (size_t i = 0, max = strlen (str); i < max; i++)
doSomethingWith (str[i]);
Sie müssen natürlich do auf Randbedingungen achten, da es sich um einen nicht signierten Typ handelt. Die Grenze am oberen Ende ist normalerweise nicht so wichtig, da das Maximum normalerweise groß ist (obwohl es ist möglich ist, dorthin zu gelangen). Die meisten Leute verwenden einfach ein int
für diese Art von Dingen, weil sie selten Strukturen oder Arrays haben, die groß genug sind, um die Kapazität dieses int
zu überschreiten.
Aber achte auf Dinge wie:
for (size_t i = strlen (str) - 1; i >= 0; i--)
dies führt zu einer Endlosschleife aufgrund des Umbruchverhaltens von Werten ohne Vorzeichen (obwohl ich gesehen habe, dass Compiler davor warnen). Dies kann auch durch Folgendes gelindert werden (etwas schwieriger zu verstehen, aber zumindest immun gegen Verpackungsprobleme):
for (size_t i = strlen (str); i-- > 0; )
Durch Verschieben der Dekrementierung in einen Nebeneffekt der Fortsetzungsbedingung nach dem Überprüfen wird der Wert before dekrementiert, wobei jedoch der dekrementierte Wert innerhalb der Schleife verwendet wird (dh warum die Schleife von len .. 1
eher, als len-1 .. 0
).
Per Definition, size_t
ist das Ergebnis des Operators sizeof
. size_t
wurde erstellt, um sich auf Größen zu beziehen.
Die Häufigkeit, mit der Sie etwas tun (10 in Ihrem Beispiel), hat nichts mit Größen zu tun. Warum also size_t
? int
oder unsigned int
, sollte in Ordnung sein.
Natürlich ist es auch wichtig, was Sie mit i
innerhalb der Schleife tun. Wenn Sie es an eine Funktion übergeben, die ein unsigned int
, zum Beispiel, wähle unsigned int
.
In jedem Fall empfehle ich, implizite Typkonvertierungen zu vermeiden. Machen Sie alle Typkonvertierungen explizit.
size_t
ist eine sehr gut lesbare Methode, um die Größenabmessung eines Elements anzugeben - Länge eines Strings, Anzahl der Bytes, die ein Zeiger benötigt usw. Es ist auch plattformübergreifend portierbar - Sie werden feststellen, dass sich 64-Bit- und 32-Bit-Funktionen gut mit Systemfunktionen verhalten und size_t
- etwas, das unsigned int
möglicherweise nicht (z. B. wann sollten Sie unsigned long
Verwenden Sie std :: size_t zum Indizieren/Zählen von Arrays im C-Stil.
Für AWL-Container haben Sie (zum Beispiel) vector<int>::size_type
, das zum Indizieren und Zählen von Vektorelementen verwendet werden soll.
In der Praxis handelt es sich in der Regel um beide nicht signierte Ints, dies ist jedoch nicht garantiert, insbesondere wenn benutzerdefinierte Zuweiser verwendet werden.
Bald werden die meisten Computer 64-Bit-Architekturen mit 64-Bit-Betriebssystemen sein, auf denen Programme ausgeführt werden, die auf Containern mit Milliarden von Elementen ausgeführt werden. Dann muss benutze size_t
anstelle von int
als Schleifenindex, andernfalls wird Ihr Index mlaufend am 2 ^ 32: ten Element auf 32- und 64-Bit-Systemen.
Bereite dich auf die Zukunft vor!
fast nie
Wann immer Sie einen Vektor mit mehr als 2 GB Zeichen auf einem 32-Bit-System benötigen. In jedem anderen Anwendungsfall ist die Verwendung eines vorzeichenbehafteten Typs viel sicherer als die Verwendung eines vorzeichenlosen Typs.
beispiel:
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]);
Das unterzeichnete Äquivalent von size_t
ist ptrdiff_t
, nicht int
. Aber die Verwendung von int
ist in den meisten Fällen immer noch viel besser als size_t. ptrdiff_t
ist long
auf 32- und 64-Bit-Systemen.
Dies bedeutet, dass Sie immer von und nach size_t konvertieren müssen, wenn Sie mit std :: -Containern interagieren, die nicht sehr schön sind. Aber auf einer laufenden Native-Konferenz erwähnten die Autoren von c ++, dass das Entwerfen von std :: vector mit einem vorzeichenlosen size_t ein Fehler war.
Wenn Ihr Compiler Sie bei impliziten Konvertierungen von ptrdiff_t nach size_t warnt, können Sie dies mit der Konstruktorsyntax explizit machen:
calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);
wenn Sie eine Sammlung ohne Einschränkung durchlaufen möchten, verwenden Sie den Bereich basierend auf:
for(const auto& d : data) {
[...]
}
hier ein paar Worte von Bjarne Stroustrup (C++ - Autorin) bei Going Native
Für einige Leute ist dieser signierte/nicht signierte Designfehler in der STL Grund genug, nicht den std :: vector zu verwenden, sondern eine eigene Implementierung.
Seien Sie bei der Verwendung von size_t vorsichtig mit dem folgenden Ausdruck
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;
}
Sie erhalten im if-Ausdruck den Wert false, unabhängig davon, welchen Wert Sie für x haben. Ich habe mehrere Tage gebraucht, um dies zu realisieren (der Code ist so einfach, dass ich keinen Komponententest durchgeführt habe), obwohl es nur wenige Minuten dauerte, bis ich die Ursache des Problems herausgefunden hatte. Ich bin mir nicht sicher, ob es besser ist, einen Wurf oder eine Null zu machen.
if ((int)(i-x) > -1 or (i-x) >= 0)
Beide Möglichkeiten sollten funktionieren. Hier ist mein Testlauf
size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;
Die Ausgabe: i-7 = 18446744073709551614 (int) (i-7) = - 2
Ich möchte die Kommentare anderer.
size_t wird von verschiedenen Bibliotheken zurückgegeben, um anzuzeigen, dass die Größe dieses Containers nicht Null ist. Sie verwenden es, wenn Sie einmal zurückkommen: 0
In Ihrem obigen Beispiel ist das Durchschleifen von size_t jedoch ein potenzieller Fehler. Folgendes berücksichtigen:
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");
}
die Verwendung von Ganzzahlen ohne Vorzeichen kann zu solchen subtilen Problemen führen. Daher bevorzuge ich imho, size_t nur zu verwenden, wenn ich mit Containern/Typen interagiere, die dies erfordern.