¿Cuándo usar std :: size_t?

201

Me pregunto si debería usar std::size_tbucles y otras cosas en lugar de int. Por ejemplo:

#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 general, ¿cuál es la mejor práctica con respecto a cuándo usar std::size_t?

nhaa123
fuente

Respuestas:

186

Una buena regla general es para cualquier cosa que necesite comparar en la condición de bucle contra algo que es naturalmente un std::size_tsí mismo.

std::size_tes el tipo de cualquier sizeofexpresión y, como se garantiza, puede expresar el tamaño máximo de cualquier objeto (incluida cualquier matriz) en C ++. Por extensión, también se garantiza que sea lo suficientemente grande para cualquier índice de matriz, por lo que es un tipo natural para un bucle por índice sobre una matriz.

Si solo cuenta hasta un número, entonces puede ser más natural usar el tipo de la variable que contiene ese número o un into unsigned int(si es lo suficientemente grande) ya que estos deberían ser de un tamaño natural para la máquina.

CB Bailey
fuente
41
Vale la pena mencionar que no usar size_tcuando debería puede conducir a errores de seguridad .
BlueRaja - Danny Pflughoeft
55
No solo es int "natural", sino que mezclar los tipos con y sin signo también puede generar errores de seguridad. Los índices sin signo son difíciles de manejar y una buena razón para usar una clase de vector personalizada.
Jo So
2
@JoSo También hay ssize_tvalores firmados.
EntangledLoops
70

size_tes el tipo de resultado del sizeofoperador.

Se usa size_tpara variables que modelan el tamaño o el índice en una matriz. size_ttransmite semántica: inmediatamente sabe que representa un tamaño en bytes o un índice, en lugar de simplemente otro entero.

Además, usar size_tpara representar un tamaño en bytes ayuda a hacer que el código sea portátil.

Gregory Pakosz
fuente
32

El size_ttipo está destinado a especificar el tamaño de algo, por lo que es natural usarlo, por ejemplo, obtener la longitud de una cadena y luego procesar cada carácter:

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

Usted no tiene que estar atento a las condiciones de contorno, por supuesto, ya que es un tipo sin signo. El límite en el extremo superior no suele ser tan importante ya que el máximo suele ser grande (aunque es posible llegar allí). La mayoría de las personas simplemente usan un intpara ese tipo de cosas porque rara vez tienen estructuras o matrices que se vuelven lo suficientemente grandes como para exceder la capacidad de eso int.

Pero ten cuidado con cosas como:

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

lo que causará un bucle infinito debido al comportamiento de ajuste de los valores sin signo (aunque he visto que los compiladores advierten sobre esto). Esto también puede ser aliviado por el (un poco más difícil de entender pero al menos inmune a los problemas de envoltura):

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

Al cambiar el decremento a un efecto secundario posterior a la verificación de la condición de continuación, esto verifica la continuación del valor antes del decremento, pero aún usa el valor decrementado dentro del bucle (por lo que el bucle se ejecuta en len .. 1lugar de len-1 .. 0).

paxdiablo
fuente
14
Por cierto, es una mala práctica recurrir strlena cada iteración de un bucle. :) Puedes hacer algo como esto:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil
1
Incluso si fuera un tipo con signo, debe tener cuidado con las condiciones de contorno, tal vez aún más, ya que el desbordamiento de entero con signo es un comportamiento indefinido.
Adrian McCarthy
2
La cuenta regresiva correcta se puede hacer de la siguiente manera (infame):for (size_t i = strlen (str); i --> 0;)
Jo So
1
@JoSo, eso es realmente un buen truco, aunque no estoy seguro de que me guste la introducción del -->operador "va a" (ver stackoverflow.com/questions/1642028/… ). Han incorporado su sugerencia en la respuesta.
paxdiablo
Se puede hacer una simple if (i == 0) break;al final del bucle (por ejemplo, for (size_t i = strlen(str) - 1; ; --i)(I como la suya mejor sin embargo, pero sólo me preguntaba si esto funcionaría igual de bien)..
RastaJedi
13

Por definición, size_tes el resultado del sizeofoperador. size_tfue creado para referirse a los tamaños.

La cantidad de veces que haces algo (10, en tu ejemplo) no se trata de tamaños, entonces ¿por qué usarlo size_t? into unsigned intdebería estar bien.

Por supuesto, también es relevante lo que haces identro del bucle. Si lo pasa a una función que toma unsigned int, por ejemplo, pick unsigned int.

En cualquier caso, recomiendo evitar las conversiones de tipo implícito. Haga explícitas todas las conversiones de tipo.

Daniel Daranas
fuente
10

size_tes una forma muy legible de especificar la dimensión de tamaño de un elemento: longitud de una cadena, cantidad de bytes que ocupa un puntero, etc. También es portátil en todas las plataformas: encontrará que 64 bits y 32 bits se comportan bien con las funciones del sistema y size_t- algo que unsigned intpodría no hacer (por ejemplo, cuándo debe usarunsigned long

Ofir
fuente
9

respuesta corta:

casi nunca

respuesta larga:

Siempre que necesite tener un vector de carbón más grande que 2 gb en un sistema de 32 bits. En cualquier otro caso de uso, usar un tipo con signo es mucho más seguro que usar un tipo sin signo.

ejemplo:

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]);

El equivalente firmado de size_tes ptrdiff_t, no int. Pero el uso intsigue siendo mucho mejor en la mayoría de los casos que size_t. ptrdiff_testá longen sistemas de 32 y 64 bits.

Esto significa que siempre debe convertir ay desde size_t cada vez que interactúa con un std :: container, lo que no es muy bonito. Pero en una conferencia nativa en curso, los autores de c ++ mencionaron que diseñar std :: vector con un size_t sin signo fue un error.

Si su compilador le da advertencias sobre conversiones implícitas de ptrdiff_t a size_t, puede hacerlo explícito con la sintaxis del constructor:

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

si solo desea iterar una colección, sin límites, utilice el rango basado en:

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

aquí algunas palabras de Bjarne Stroustrup (autor de C ++) al volverse nativo

Para algunas personas, este error de diseño firmado / no firmado en el STL es motivo suficiente para no utilizar el std :: vector, sino una implementación propia.

Arne
fuente
1
Entiendo de dónde vienen, pero sigo pensando que es raro escribir for(int i = 0; i < get_size_of_stuff(); i++). Ahora, claro, es posible que no quieras hacer muchos bucles sin procesar, pero, vamos, también los usas.
einpoklum
La única razón por la que uso bucles sin formato es porque la biblioteca de algoritmos de c ++ está bastante mal diseñada. Hay idiomas, como Scala, que tienen una biblioteca mucho mejor y más evolucionada para operar en colecciones. Entonces, el caso de uso de los bucles sin procesar se elimina prácticamente. También hay enfoques para mejorar c ++ con un STL nuevo y mejor, pero dudo que esto suceda en la próxima década.
Arne
1
Me sale sin firmar i = 0; afirmar (i-1, MAX_INT); pero no entiendo por qué dices "si ya tuve un flujo inferior, esto se hace realidad" porque el comportamiento de la aritmética en ints sin signo siempre está definido, es decir. el resultado es el módulo de resultados del tamaño del entero representable más grande. Entonces, si i == 0, entonces i-- se convierte en MAX_INT y luego i ++ se convierte en 0 nuevamente.
mabraham
@mabraham Miré cuidadosamente, y tienes razón, mi código no es el mejor para mostrar el problema. Normalmente esto es x + 1 < yequivalente a x < y - 1, pero no están con enteros no genéricos. Eso puede introducir errores fácilmente cuando se transforman cosas que se supone que son equivalentes.
Arne
8

Use std :: size_t para indexar / contar matrices de estilo C.

Para los contenedores STL, tendrá (por ejemplo) vector<int>::size_type, que debe usarse para indexar y contar elementos vectoriales.

En la práctica, generalmente son ints sin signo, pero no está garantizado, especialmente cuando se usan asignadores personalizados.

Peter Alexander
fuente
2
Con gcc en linux, std::size_tgeneralmente es unsigned long(8 bytes en sistemas de 64 bits) en lugar de unisgned int(4 bytes).
rafak
55
Sin size_tembargo, las matrices de estilo C no están indexadas , ya que los índices pueden ser negativos. Sin embargo, se podría usar size_tpara la propia instancia de dicha matriz si no se quiere ser negativo.
Johannes Schaub - litb
¿Las comparaciones en u64s son tan rápidas como las comparaciones en u32s? He cronometrado severas sanciones de rendimiento por usar u8s y u16s como centinelas de bucle, pero no sé si Intel ha actuado juntos en 64s.
Crashworks
2
Dado que la indexación de matriz de estilo C es equivalente a usar el operador +en punteros, parece que ptrdiff_tes el que se debe usar para los índices.
Pavel Minaev
8
En cuanto a vector<T>::size_type(y lo mismo ocurre con todos los otros contenedores), en realidad es bastante inútil, porque está garantizado efectivamente que es size_t- está tipificado Allocator::size_type, y para restricciones sobre eso con respecto a los contenedores, ver 20.1.5 / 4 - en particular, size_typedebe ser size_ty difference_typedebe ser ptrdiff_t. Por supuesto, el valor predeterminado std::allocator<T>cumple esos requisitos. Así que usa el más corto size_ty no te molestes con el resto del lote :)
Pavel Minaev
7

Pronto, la mayoría de las computadoras serán arquitecturas de 64 bits con SO de 64 bits que ejecutan programas que operan en contenedores de miles de millones de elementos. A continuación, se debe utilizar size_ten lugar de intcomo índice del bucle, de lo contrario su índice va a envolver alrededor de la 2 ^ 32: ésimo elemento, tanto en sistemas de 32 y 64 bits.

¡Prepárate para el futuro!

Nordlöw
fuente
Su argumento solo va tan lejos como el significado que uno necesita en long intlugar de un int. Si size_tes relevante en un sistema operativo de 64 bits, era igual de relevante en un sistema operativo de 32 bits.
einpoklum
4

Cuando use size_t tenga cuidado con la siguiente expresión

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

Obtendrá falso en la expresión if, independientemente del valor que tenga para x. Me tomó varios días darme cuenta de esto (el código es tan simple que no hice la prueba de la unidad), aunque solo me llevó unos minutos determinar el origen del problema. No estoy seguro de que sea mejor hacer un lanzamiento o usar cero.

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

Ambas formas deberían funcionar. Aquí está mi prueba de funcionamiento

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

La salida: i-7 = 18446744073709551614 (int) (i-7) = - 2

Me gustaría los comentarios de otros.

Kemin Zhou
fuente
2
tenga en cuenta que se (int)(i - 7)trata de un flujo inferior que se convierte intdespués, mientras int(i) - 7que no es un flujo inferior ya que primero convierte ia an int, y luego resta 7. Además, encontré su ejemplo confuso.
hochl
Mi punto es que int es generalmente más seguro cuando haces restas.
Kemin Zhou
4

size_t es devuelto por varias bibliotecas para indicar que el tamaño de ese contenedor no es cero. Lo usas cuando regresas: 0

Sin embargo, en el ejemplo anterior, hacer un loop en size_t es un error potencial. Considera lo siguiente:

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

El uso de enteros sin signo tiene el potencial de crear este tipo de problemas sutiles. Por lo tanto, prefiero usar size_t solo cuando interactúo con contenedores / tipos que lo requieren.

ascotan
fuente
Todos parecen usar size_t en bucle sin preocuparse por este error, y aprendí esto de la manera difícil
Pranjal Gupta,
-2

size_tes un tipo sin signo que puede contener el valor entero máximo para su arquitectura, por lo que está protegido contra desbordamientos de enteros debido al signo (el signo int 0x7FFFFFFFincrementado en 1 le dará -1) o el tamaño corto (el signo corto sin signo int 0xFFFF incrementado en 1 le dará 0).

Se utiliza principalmente en indexación de matrices / bucles / aritmética de direcciones, etc. Funciones como memset()y por igual size_tsolo aceptan , porque teóricamente puede tener un bloque de memoria de tamaño 2^32-1(en la plataforma de 32 bits).

Para tales bucles simples, no se moleste y use solo int.

Wizzard
fuente
-3

size_t es un tipo integral sin signo, que puede representar el número entero más grande en su sistema. Úselo solo si necesita matrices muy grandes, matrices, etc.

Algunas funciones devuelven un size_t y su compilador le avisará si intenta hacer comparaciones.

Evite eso utilizando el tipo de datos firmado / sin firmar apropiado o simplemente escribiendo para un hack rápido.

Rey Mono
fuente
44
Úselo solo si desea evitar errores y agujeros de seguridad.
Craig McQueen
2
Es posible que en realidad no pueda representar el número entero más grande en su sistema.
Adrian McCarthy
-4

size_t es unsigned int. así que cuando quieras int sin firmar puedes usarlo.

Lo uso cuando quiero especificar el tamaño de la matriz, el contador ect ...

void * operator new (size_t size); is a good use of it.
Ashish
fuente
10
En realidad, no es necesariamente lo mismo que unsigned int. No está firmado, pero podría ser más grande (o supongo que más pequeño, aunque no conozco ninguna plataforma en la que esto sea cierto) que un int.
Todd Gamblin
Por ejemplo, en una máquina de 64 bits size_tpuede ser un entero de 64 bits sin signo, mientras que en una máquina de 32 bits es solo un entero sin signo de 32 bits.
HerpDerpington