En C ++, size_t
(o, más correctamente, T::size_type
que es "generalmente" size_t
; es decir, un unsigned
tipo) se usa como el valor de retorno para size()
, el argumento a operator[]
, etc. (ver std::vector
, et. Al.)
Por otro lado, los lenguajes .NET usan int
(y, opcionalmente long
) para el mismo propósito; de hecho, los lenguajes compatibles con CLS no son necesarios para admitir tipos sin firmar .
Dado que .NET es más nuevo que C ++, algo me dice que puede haber problemas al usar unsigned int
incluso cosas que "no pueden" ser negativas, como un índice de matriz o una longitud. ¿Es el enfoque de C ++ "artefacto histórico" para la compatibilidad con versiones anteriores? ¿O hay compensaciones de diseño reales y significativas entre los dos enfoques?
¿Por qué importa esto? Bueno ... ¿qué debo usar para una nueva clase multidimensional en C ++; size_t
o int
?
struct Foo final // e.g., image, matrix, etc.
{
typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
typedef size_t size_type; // c.f., std::vector<>
dimension_type bar_; // maybe rows, or x
dimension_type baz_; // e.g., columns, or y
size_type size() const { ... } // STL-like interface
};
-1
se devuelve desde funciones que devuelven un índice, para indicar "no encontrado" o "fuera de rango". También se devuelve de lasCompare()
funciones (implementaciónIComparable
). Un int de 32 bits se considera ir a escribir para un número general, por lo que espero sean razones obvias.Respuestas:
Si. Para ciertos tipos de aplicaciones, como el procesamiento de imágenes o el procesamiento de matrices, a menudo es necesario acceder a elementos relativos a la posición actual:
En este tipo de aplicaciones, no puede realizar la verificación de rango con enteros sin signo sin pensar detenidamente:
En su lugar, debe reorganizar su expresión de verificación de rango. Esa es la principal diferencia. Los programadores también deben recordar las reglas de conversión de enteros. En caso de duda, vuelva a leer http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
Muchas aplicaciones no necesitan usar índices de matriz muy grandes, pero sí necesitan realizar comprobaciones de rango. Además, muchos programadores no están entrenados para hacer esta gimnasia de reordenamiento de expresiones. Una sola oportunidad perdida abre la puerta a una hazaña.
C # está diseñado para aquellas aplicaciones que no necesitarán más de 2 ^ 31 elementos por matriz. Por ejemplo, una aplicación de hoja de cálculo no necesita lidiar con tantas filas, columnas o celdas. C # se ocupa del límite superior al tener aritmética comprobada opcional que se puede habilitar para un bloque de código con una palabra clave sin alterar las opciones del compilador. Por esta razón, C # favorece el uso de enteros con signo. Cuando estas decisiones se consideran por completo, tiene sentido.
C ++ es simplemente diferente y es más difícil obtener el código correcto.
Con respecto a la importancia práctica de permitir que la aritmética firmada elimine una posible violación del "principio del menor asombro", un caso en cuestión es OpenCV, que utiliza un entero de 32 bits con signo para el índice de elementos de la matriz, el tamaño de la matriz, el recuento de canales de píxeles, etc. Imagen El procesamiento es un ejemplo de dominio de programación que utiliza mucho el índice de matriz relativa. El desbordamiento de enteros sin signo (resultado negativo envuelto) complicará gravemente la implementación del algoritmo.
fuente
Esta respuesta realmente depende de quién va a usar su código y qué estándares quieren ver.
size_t
es un tamaño entero con un propósito:Por lo tanto, cada vez que desee trabajar con el tamaño de los objetos en bytes, debe usar
size_t
. Ahora, en muchos casos, no está usando estas dimensiones / índices para contar bytes, pero la mayoría de los desarrolladores eligen usarsize_t
para mantener la coherencia.Tenga en cuenta que siempre debe usar
size_t
si su clase está diseñada para tener el aspecto de una clase STL. Todas las clases de STL en la especificación usansize_t
. Es válido para el compilador para typedefsize_t
a serunsigned int
, y también es válida para que pueda ser typedefed aunsigned long
. Si usaint
olong
directamente, eventualmente se encontrará con compiladores donde una persona que cree que su clase siguió el estilo de STL queda atrapada porque no siguió el estándar.En cuanto al uso de tipos firmados, hay algunas ventajas:
int
, pero es mucho más difícil saturar el códigounsigned int
.int32_t
yuint32_t
). Esto puede simplificar la interoperabilidad APILa gran desventaja de los tipos firmados es la obvia: pierde la mitad de su dominio. Un número con signo no puede contar tan alto como un número sin signo. Cuando apareció C / C ++, esto fue muy importante. Uno debía ser capaz de abordar la capacidad total del procesador, y para hacerlo era necesario usar números sin firmar.
Para los tipos de aplicaciones dirigidas a .NET, no era tan necesaria la necesidad de un índice sin firmar de dominio completo. Muchos de los propósitos para tales números son simplemente inválidos en un lenguaje administrado (me viene a la mente la agrupación de memoria). Además, cuando salió .NET, las computadoras de 64 bits eran claramente el futuro. Estamos muy lejos de necesitar el rango completo de un entero de 64 bits, por lo que sacrificar un bit ya no es tan doloroso como antes. Si realmente necesita 4 mil millones de índices, simplemente cambie a usar enteros de 64 bits. En el peor de los casos, lo ejecuta en una máquina de 32 bits y es un poco lento.
Veo el comercio como uno de conveniencia. Si tiene suficiente potencia informática que no le importa desperdiciar un poco de su tipo de índice que nunca usará, entonces es conveniente simplemente escribir
int
olong
alejarse de él. Si crees que realmente querías ese último bit, entonces probablemente deberías haber prestado atención a la firma de tus números.fuente
size()
wasreturn bar_ * baz_;
; ¿No crea eso ahora un problema potencial con el desbordamiento de enteros (envoltura) que no tendría si no lo usarasize_t
?bar_ * baz_
pueda desbordar un entero con signo pero no un entero sin signo. Limitándonos a C ++, vale la pena señalar que el desbordamiento sin signo está definido en la especificación, pero el desbordamiento con signo es un comportamiento indefinido, por lo que si el módulo aritmético de enteros sin signo es deseable, definitivamente úselos, ¡porque está realmente definido!size()
desbordó el firmado multiplicación, estás en la tierra lenguaje UB. (y en elfwrapv
modo, ver a continuación :) Cuando entonces , con solo un poquito más, desbordó la multiplicación sin signo , en la tierra de error de código de usuario, devolvería un tamaño falso. Así que no creo que sin firmar compre mucho aquí.Creo que la respuesta de rwong anterior ya destaca de manera excelente los problemas.
Agregaré mi 002:
size_t
, es decir, un tamaño que ...... solo se requiere para los índices de rango cuando
sizeof(type)==1
, es decir, si se trata dechar
tipos de byte ( ). (Pero, notamos, puede ser más pequeño que un tipo ptr :xxx::size_type
podría usarse en el 99.9% de los casos, incluso si fuera un tipo de tamaño con signo. (compararssize_t
)std::vector
amigos elijansize_t
, un tipo sin signo , para el tamaño y la indexación es un defecto de diseño. Estoy de acuerdo. (En serio, tómese 5 minutos y vea la charla relámpago CppCon 2016: Jon Kalb "sin firmar: Una directriz para un mejor código" .)size_t
para ser coherente con la Biblioteca estándar, o use (con firma )intptr_t
ossize_t
para cálculos de indexación fáciles y menos propensos a errores.intptr_t
si desea firmar y desea el tamaño de palabra de la máquina, o usessize_t
.Para responder directamente a la pregunta, no se trata completamente de un "artefacto histórico", ya que el tema teórico de la necesidad de abordar más de la mitad del espacio de direcciones ("indexación" o) debe abordarse de alguna manera en un lenguaje de bajo nivel como C ++.
En retrospectiva, personalmente , creo que es un defecto de diseño que la Biblioteca estándar utiliza sin firmar en
size_t
todo el lugar, incluso donde no representa un tamaño de memoria en bruto, sino una capacidad de datos escritos, como para las colecciones:Repito el consejo de Jon aquí:
(* 1) es decir, unsigned == bitmask, nunca hagas cálculos matemáticos (aquí aparece la primera excepción; es posible que necesites un contador que se envuelva; este debe ser un tipo sin signo).
(* 2) cantidades que significan algo con lo que cuenta y / o hace matemáticas.
fuente
ssize_t
, definido como el colgante firmado ensize_t
lugar deintptr_t
, que puede almacenar cualquier puntero (no miembro) y, por lo tanto, podría ser más grande?size_t
definición. Ver size_t vs. intptr y en.cppreference.com/w/cpp/types/size_t Aprendí algo nuevo hoy. :-) Creo que el resto de los argumentos se mantienen, veré si puedo arreglar los tipos utilizados.Solo agregaré eso por razones de rendimiento que normalmente uso size_t, para asegurar que los errores de cálculo causen un flujo inferior, lo que significa que ambas comprobaciones de rango (por debajo de cero y por encima del tamaño ()) se pueden reducir a uno:
usando int firmado:
usando unsigned int:
fuente