Quand utiliser std :: size_t?

201

Je me demande simplement si je devrais utiliser std::size_tdes boucles et d'autres choses 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 concernant le moment de l'utilisation std::size_t?

nhaa123
la source

Réponses:

186

Une bonne règle de base est pour tout ce que vous devez comparer dans la condition de boucle à quelque chose qui est naturellement un std::size_tlui - même.

std::size_test le type de n'importe quelle sizeofexpression et as est garanti pour pouvoir exprimer la taille maximale de n'importe quel objet (y compris n'importe quel tableau) en C ++. Par extension, il est également garanti d'être suffisamment grand pour tout index de tableau, c'est donc un type naturel pour une boucle par index sur un tableau.

Si vous comptez seulement jusqu'à un nombre, il peut être plus naturel d'utiliser soit le type de la variable qui contient ce nombre, soit un intou unsigned int(s'il est suffisamment grand) car ceux-ci devraient être une taille naturelle pour la machine.

CB Bailey
la source
41
Il convient de mentionner que le fait de ne pas l' utiliser size_tquand vous le devriez peut entraîner des bogues de sécurité .
BlueRaja - Danny Pflughoeft
5
Non seulement est-il «naturel», mais le mélange de types signés et non signés peut également entraîner des bogues de sécurité. Les indices non signés sont difficiles à gérer et une bonne raison d'utiliser une classe vectorielle personnalisée.
Jo So
2
@JoSo Il y a aussi ssize_tpour les valeurs signées.
EntangledLoops
70

size_test le type de résultat de l' sizeofopérateur.

À utiliser size_tpour les variables qui modélisent la taille ou l'index dans un tableau. size_ttransmet la sémantique: vous savez immédiatement qu'elle représente une taille en octets ou un index, plutôt qu'un simple autre entier.

De plus, utiliser size_tpour représenter une taille en octets aide à rendre le code portable.

Gregory Pakosz
la source
32

Le size_ttype est destiné à spécifier la taille de quelque chose, il est donc naturel de l'utiliser, par exemple, pour obtenir la longueur d'une chaîne, puis pour traiter chaque caractère:

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

Vous ne devez surveiller les conditions aux limites bien sûr, car il est un type non signé. La limite à l'extrémité supérieure n'est généralement pas si importante car le maximum est généralement grand (bien qu'il soit possible de s'y rendre). La plupart des gens utilisent juste un intpour ce genre de chose car ils ont rarement des structures ou des tableaux qui deviennent suffisamment grands pour dépasser la capacité de cela int.

Mais attention aux choses comme:

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

ce qui provoquera une boucle infinie en raison du comportement d'habillage des valeurs non signées (bien que 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 à l'abri des problèmes d'emballage):

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

En décalant la décrémentation dans un effet secondaire post-vérification de la condition de continuation, cela vérifie la continuation de la valeur avant décrémentation, mais utilise toujours la valeur décrémentée à l'intérieur de la boucle (c'est pourquoi la boucle s'exécute len .. 1plutôt que len-1 .. 0).

paxdiablo
la source
14
Au fait, c'est une mauvaise pratique d'appeler strlenà chaque itération d'une boucle. :) Vous pouvez faire quelque chose comme ça:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil
1
Même s'il s'agissait d'un type signé, vous devez faire attention aux conditions aux limites, peut-être plus encore, car le dépassement d'entier signé est un comportement non défini.
Adrian McCarthy
2
Le décompte correct peut se faire de la manière (infâme) suivante:for (size_t i = strlen (str); i --> 0;)
Jo So
1
@JoSo, c'est en fait une astuce intéressante, mais je ne suis pas sûr que j'aime l'introduction de l' -->opérateur "va à" (voir stackoverflow.com/questions/1642028/… ). Vous avez intégré votre suggestion dans la réponse.
paxdiablo
Pouvez-vous faire un simple if (i == 0) break;à la fin de la boucle for (par exemple, for (size_t i = strlen(str) - 1; ; --i)(j'aime mieux le vôtre, mais je me demande simplement si cela fonctionnerait aussi bien).
RastaJedi
13

Par définition, size_test le résultat de l' sizeofopérateur.size_ta été créé pour faire référence aux tailles.

Le nombre de fois que vous faites quelque chose (10, dans votre exemple) ne concerne pas les tailles, alors pourquoi l'utiliser size_t? int, ouunsigned int , ça devrait aller.

Bien sûr, il est également pertinent de savoir ce que vous faites à l' iintérieur de la boucle. Si vous le passez à une fonction qui prend unsigned int, par exemple, uneunsigned int .

Dans tous les cas, je recommande d'éviter les conversions de types implicites. Rendez toutes les conversions de types explicites.

Daniel Daranas
la source
10

size_test une façon très lisible pour spécifier la dimension de la taille d'un élément - longueur d'une chaîne, la quantité d'octets un pointeur prend, etc. Il est également portable sur toutes les plateformes - vous constaterez que 64bit et 32bit deux se comportent bien avec les fonctions du système et size_t- quelque chose qui unsigned intpourrait ne pas faire (par exemple, quand devriez-vous utiliserunsigned long

Ofir
la source
9

réponse courte:

presque jamais

longue réponse:

Chaque fois que vous avez besoin d'un vecteur de caractères supérieur à 2 Go sur un système 32 bits. Dans tous les autres cas d'utilisation, l'utilisation d'un type signé est beaucoup plus sûre que l'utilisation d'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_test ptrdiff_t, non int. Mais l'utilisation intest toujours bien meilleure dans la plupart des cas que size_t. ptrdiff_testlong sur les systèmes 32 et 64 bits.

Cela signifie que vous devez toujours convertir vers et depuis 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 la conception de std :: vector avec un size_t non signé était une erreur.

Si votre compilateur vous donne des avertissements sur les conversions implicites de ptrdiff_t à size_t, vous pouvez le rendre explicite avec la syntaxe du constructeur:

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

si vous voulez simplement itérer une collection, sans limites, utilisez la plage basée sur:

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

voici quelques mots de Bjarne Stroustrup (auteur C ++) à devenir natif

Pour certaines personnes, cette erreur de conception signée / non signée dans la STL est une raison suffisante pour ne pas utiliser le vecteur std ::, mais plutôt une implémentation propre.

Arne
la source
1
Je comprends d'où ils viennent, mais je pense toujours que c'est bizarre d'écrire for(int i = 0; i < get_size_of_stuff(); i++). Maintenant, bien sûr, vous ne voudrez peut-être pas faire beaucoup de boucles brutes, mais - allez, vous les utilisez aussi.
einpoklum
La seule raison pour laquelle j'utilise des boucles brutes, c'est que la bibliothèque d'algorithmes c ++ est assez mal conçue. Il existe des langages, comme Scala, qui ont une bibliothèque bien meilleure et plus évoluée pour fonctionner sur les collections. Ensuite, le cas d'utilisation des boucles brutes est à peu près éliminé. Il existe également des approches pour améliorer le c ++ avec une nouvelle et meilleure STL, mais je doute que cela se produise au cours de la prochaine décennie.
Arne
1
J'obtiens ce non signé i = 0; assert (i-1, MAX_INT); mais je ne comprends pas pourquoi vous dites "si j'ai déjà eu un sous-dépassement, cela devient vrai" car le comportement de l'arithmétique sur les entiers non signés est toujours défini, c'est-à-dire. le résultat est le résultat modulo de la taille du plus grand entier représentable. Donc, si i == 0, alors i-- devient MAX_INT, puis i ++ redevient 0.
mabraham
@mabraham J'ai regardé attentivement, et vous avez raison, mon code n'est pas le meilleur pour montrer le problème. Normalement, cela x + 1 < yéquivaut à x < y - 1, mais ils ne sont pas avec des entiers non-tendeurs. Cela peut facilement introduire des bogues lorsque des choses sont transformées et supposées équivalentes.
Arne
8

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 l'indexation et le comptage des éléments vectoriels.

En pratique, il s'agit généralement de deux entiers non signés, mais cela n'est pas garanti, en particulier lors de l'utilisation d'allocateurs personnalisés.

Peter Alexander
la source
2
Avec gcc sur linux, std::size_test généralement unsigned long(8 octets sur les systèmes 64 bits) plutôt que unisgned int(4 octets).
rafak
5
Les tableaux de style C ne sont cependant pas indexés size_t, car les index peuvent être négatifs. Cependant, on pourrait utiliser size_tsa propre instance d'un tel tableau si l'on ne veut pas devenir négatif.
Johannes Schaub - litb
Les comparaisons sur u64 sont-elles aussi rapides que les comparaisons sur u32? J'ai chronométré de sévères pénalités de performance pour utiliser des u8 et des u16 comme sentinelles en boucle, mais je ne sais pas si Intel a réussi à se mettre au jeu sur les 64.
Crashworks
2
Étant donné que l'indexation de tableau de style C équivaut à utiliser l'opérateur +sur des pointeurs, il semblerait que ce ptrdiff_tsoit celui à utiliser pour les index.
Pavel Minaev,
8
Quant à vector<T>::size_type(et idem pour tous les autres conteneurs), il est en fait plutôt inutile, car il est effectivement garanti d'être size_t- il est typé Allocator::size_typeet, pour les restrictions concernant les conteneurs, voir 20.1.5 / 4 - en particulier, size_typedoit être size_tet difference_typedoit être ptrdiff_t. Bien sûr, la valeur par défaut std::allocator<T>satisfait ces exigences. Alors utilisez simplement le plus court size_tet ne vous embêtez pas avec le reste du lot :)
Pavel Minaev
7

Bientôt, la plupart des ordinateurs seront des architectures 64 bits avec un système d'exploitation 64 bits: exécutant des programmes fonctionnant sur des conteneurs de milliards d'éléments. Ensuite, vous devez utiliser size_tau lieu d' intun index de boucle, sinon votre index se terminera à l'élément 2 ^ 32: th, sur les systèmes 32 et 64 bits.

Préparez-vous pour l'avenir!

Nordlöw
la source
Votre argument ne va que jusqu'à ce que l'on a besoin d'un long intplutôt que d'un int. Si size_test pertinent sur un système d'exploitation 64 bits, il était tout aussi pertinent sur un système d'exploitation 32 bits.
einpoklum
4

Lorsque vous utilisez size_t, soyez prudent avec 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 faux dans l'expression if, quelle que soit la valeur que vous avez pour x. Il m'a fallu plusieurs jours pour m'en rendre compte (le code est si simple que je n'ai pas fait de test unitaire), même si cela ne prend que quelques minutes pour comprendre la source du problème. Pas sûr qu'il vaut mieux faire un cast ou utiliser zéro.

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

Les deux façons devraient fonctionner. Voici mon test

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 les commentaires des autres.

Kemin Zhou
la source
2
veuillez noter qu'il (int)(i - 7)s'agit d'un sous-dépassement qui est converti par la intsuite, alors qu'il int(i) - 7ne s'agit pas d'un sous-dépassement puisque vous convertissez d'abord ien un int, puis soustrayez 7. De plus, j'ai trouvé votre exemple déroutant.
hochl
Mon point est que int est généralement plus sûr lorsque vous effectuez des soustractions.
Kemin Zhou
4

size_t est renvoyé par diverses bibliothèques pour indiquer que la taille de ce conteneur n'est pas nulle. Vous l'utilisez à votre retour: 0

Cependant, dans votre exemple ci-dessus, une boucle 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 a le potentiel de créer ces types de problèmes subtils. Par conséquent, à mon humble avis, je préfère utiliser size_t uniquement lorsque j'interagis avec les conteneurs / types qui en ont besoin.

ascotan
la source
Tout le monde semble utiliser size_t en boucle sans se soucier de ce bug, et je l'ai appris à la dure
Pranjal Gupta
-2

size_test un type non signé qui peut contenir une valeur entière maximale pour votre architecture, il est donc protégé contre les débordements d'entiers dus à la signature (signé int 0x7FFFFFFFincrémenté de 1 vous donnera -1) ou à la taille courte (unsigned short int 0xFFFF incrémenté de 1 vous donnera 0).

Il est principalement utilisé dans l'indexation de tableaux / boucles / arithmétiques d'adresses, etc. Les fonctions similaires memset()et similaires acceptent size_tuniquement, car théoriquement, vous pouvez avoir un bloc de mémoire de taille 2^32-1(sur une plate-forme 32 bits).

Pour de telles boucles simples, ne vous embêtez pas et utilisez simplement int.

Wizzard
la source
-3

size_t est un type intégral non signé, qui peut représenter le plus grand entier sur votre système. Utilisez-le uniquement si vous avez besoin de très grands tableaux, matrices, etc.

Certaines fonctions renvoient un size_t et votre compilateur vous avertira si vous essayez de faire des comparaisons.

Évitez cela en utilisant un type de données signé / non signé approprié ou simplement transtypé pour un hack rapide.

roi Singe
la source
4
Utilisez-le uniquement si vous souhaitez éviter les bugs et les failles de sécurité.
Craig McQueen
2
Il ne peut pas réellement représenter le plus grand entier sur votre système.
Adrian McCarthy
-4

size_t n'est pas signé int. donc chaque fois que vous voulez un entier non signé, vous pouvez l'utiliser.

Je l'utilise quand je veux spécifier la taille du tableau, le compteur ect ...

void * operator new (size_t size); is a good use of it.
Ashish
la source
10
En fait, ce n'est pas nécessairement la même chose que unsigned int. Il n'est pas signé, mais il pourrait être plus grand (ou je suppose que plus petit bien que je ne connaisse aucune plate-forme où cela soit vrai) qu'un int.
Todd Gamblin,
Par exemple, sur une machine 64 bits, il size_tpeut s'agir d'un entier 64 bits non signé, tandis que sur une machine 32 bits, il ne s'agit que d'un entier non signé 32 bits.
HerpDerpington