Je me demande juste si je devrais utiliser std::size_t
pour les boucles et le matériel au lieu de int
? Par exemple:
#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?
}
}
En général, quelle est la meilleure pratique pour savoir quand utiliser std::size_t
?
Une bonne règle générale s'applique à tout ce que vous devez comparer dans l'état de boucle à un élément qui est naturellement un std::size_t
lui-même.
std::size_t
est le type de toute expression sizeof
et il est garanti qu’il est possible d’exprimer la taille maximale de n’importe quel objet (y compris tout tableau) en C++. Par extension, il est également garanti que sa taille est suffisante pour tout index de tableau. Il s'agit donc d'un type naturel pour une boucle indexée sur un tableau.
Si vous comptez jusqu’à un nombre, il peut être plus naturel d’utiliser le type de la variable qui contient ce nombre ou un int
ou unsigned int
(si elle est suffisamment grande) car elles doivent avoir une taille naturelle pour la machine.
size_t
est le type de résultat de l'opérateur sizeof
.
Utilisation size_t
pour les variables qui modélisent la taille ou l’index dans un tableau. size_t
exprime la sémantique: vous savez immédiatement qu’elle représente une taille en octets ou en index, plutôt qu’un autre entier.
De plus, en utilisant size_t
représenter une taille en octets aide à rendre le code portable.
Le size_t
type est destiné à spécifier la taille de quelque chose, il est donc naturel de l'utiliser, par exemple, obtenir la longueur d'une chaîne, puis traiter chaque caractère:
for (size_t i = 0, max = strlen (str); i < max; i++)
doSomethingWith (str[i]);
Vous do devez vous méfier des conditions aux limites, car il s'agit d'un type non signé. La limite en haut n’est généralement pas très importante car le maximum est généralement grand (même s’il est est possible de l’atteindre). La plupart des gens utilisent simplement un int
pour ce genre de chose car ils ont rarement des structures ou des tableaux assez grands pour dépasser la capacité de ce int
.
Mais faites attention aux choses comme:
for (size_t i = strlen (str) - 1; i >= 0; i--)
ce qui causera une boucle infinie à cause du comportement de retour à la ligne des valeurs non signées (même si j'ai vu des compilateurs mettre en garde contre cela). Cela peut également être atténué par le (légèrement plus difficile à comprendre mais au moins immunisé contre les problèmes d’emballage):
for (size_t i = strlen (str); i-- > 0; )
En décalant la décrémentation en un effet secondaire post-vérification de la condition de continuation, la vérification de la poursuite de la valeur before décrémentée, mais utilise toujours la valeur décrémentée à l'intérieur de la boucle la boucle court de len .. 1
plutôt que len-1 .. 0
).
Par définition, size_t
est le résultat de l'opérateur sizeof
. size_t
a été créé pour faire référence aux tailles.
Le nombre de fois que vous faites quelque chose (10, dans votre exemple) n’est pas une question de taille, alors pourquoi utiliser size_t
? int
ou unsigned int
, ça devrait être bon.
Bien entendu, ce que vous faites avec i
dans la boucle est également pertinent. Si vous le passez à une fonction qui prend un unsigned int
, par exemple, choisissez unsigned int
.
Dans tous les cas, je recommande d'éviter les conversions de type implicites. Rendre toutes les conversions de types explicites.
size_t
est un moyen très lisible de spécifier la dimension de la taille d’un élément - longueur d’une chaîne, nombre d’octets utilisés par un pointeur, etc. et size_t
- quelque chose que unsigned int
pourrait ne pas le faire (par exemple, quand utiliser unsigned long
Utilisez std :: size_t pour indexer/compter les tableaux de style C.
Pour les conteneurs STL, vous aurez (par exemple) vector<int>::size_type
, qui devrait être utilisé pour indexer et compter les éléments vectoriels.
En pratique, il s’agit généralement d’entités non signées, mais cela n’est pas garanti, en particulier lors de l’utilisation d’allocateurs personnalisés.
Bientôt, la plupart des ordinateurs seront des architectures 64 bits avec un système d’exploitation 64 bits: ils exécutent des programmes fonctionnant sur des conteneurs de milliards d’éléments. Alors vous devez utiliser size_t
au lieu de int
en tant qu’index de boucle, sinon votre index sera renvoyé à l’envers à l’élément 2 ^ 32: ème, sur les systèmes 32 et 64 bits.
Préparez-vous pour l'avenir!
presque jamais
Chaque fois que vous avez besoin d'un vecteur de caractère supérieur à 2 Go sur un système 32 bits. Dans tous les autres cas d'utilisation, utiliser un type signé est beaucoup plus sûr que d'utiliser un type non signé.
exemple:
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]);
L'équivalent signé de size_t
est ptrdiff_t
, pas int
. Mais utiliser int
est toujours bien meilleur que size_t. ptrdiff_t
est long
sur des systèmes 32 et 64 bits.
Cela signifie que vous devez toujours convertir depuis et vers size_t chaque fois que vous interagissez avec un conteneur std ::, ce qui n'est pas très beau. Mais lors d’une conférence native en cours, les auteurs de c ++ ont mentionné que concevoir std :: vector avec un size_t non signé était une erreur.
Si votre compilateur vous avertit des conversions implicites de ptrdiff_t en size_t, vous pouvez le rendre explicite à l'aide de la syntaxe du constructeur:
calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);
si vous voulez juste itérer une collection, sans limite de contrôle, utilisez la plage basée sur:
for(const auto& d : data) {
[...]
}
voici quelques mots de Bjarne Stroustrup (auteur C++) at en natif
Pour certaines personnes, cette erreur de conception signé/non signé dans la STL est une raison suffisante pour ne pas utiliser le vecteur std ::, mais plutôt une implémentation propre.
Lorsque vous utilisez size_t, faites attention à l'expression suivante
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;
}
Vous obtiendrez false dans l'expression if, quelle que soit la valeur que vous avez pour x. Il m'a fallu plusieurs jours pour réaliser cela (le code est si simple que je n'ai pas fait de test unitaire), bien que cela ne prenne que quelques minutes pour comprendre la source du problème. Pas sûr qu'il soit préférable de faire un casting ou d'utiliser zéro.
if ((int)(i-x) > -1 or (i-x) >= 0)
Les deux manières devraient fonctionner. Voici mon essai
size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;
La sortie: i-7 = 18446744073709551614 (int) (i-7) = - 2
J'aimerais avoir les commentaires des autres.
size_t est renvoyé par diverses bibliothèques pour indiquer que la taille de ce conteneur est différente de zéro. Vous l'utilisez quand vous revenez une fois: 0
Cependant, dans l'exemple ci-dessus, boucler sur un size_t est un bogue potentiel. Considérer ce qui suit:
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");
}
l'utilisation d'entiers non signés peut potentiellement créer ce type de problèmes subtils. Par conséquent, je préfère utiliser size_t uniquement lorsque j'interagis avec les conteneurs/types qui en ont besoin.