size_t o int para dimensiones, índice, etc.

15

En C ++, size_t (o, más correctamente, T::size_type que es "generalmente" size_t ; es decir, un tipo unsigned ) se usa como el valor de retorno para size() , el argumento para operator[] , etc. (consulte std::vector , et al.)

Por otra parte, los lenguajes .NET usan int (y, opcionalmente, long ) para el mismo propósito; de hecho, los idiomas compatibles con CLS son no es necesario para admitir tipos sin firma / a>.

Dado que .NET es más nuevo que C ++, algo me dice que puede haber problemas usando unsigned int incluso para cosas que "no pueden" ser negativas como un índice de matriz o longitud. ¿Es el enfoque de C ++ "artefacto histórico" para compatibilidad con versiones anteriores? ¿O hay compensaciones de diseño reales y significativas entre los dos enfoques?

¿Por qué esto importa? 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
};
    
pregunta Ðаn 13.12.2016 - 21:58
fuente

4 respuestas

9
  

Dado que .NET es más nuevo que C ++, algo me dice que puede haber problemas al usar int sin firmar incluso para cosas que "no pueden" ser negativas como un índice de matriz o una longitud.

Sí. Para ciertos tipos de aplicaciones, como el procesamiento de imágenes o el procesamiento de matrices, a menudo es necesario acceder a elementos relacionados con la posición actual:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

En este tipo de aplicaciones, no puede realizar una verificación de rango con enteros sin signo sin pensarlo detenidamente:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

En su lugar, debes cambiar el rango de tu 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 enlace

Muchas aplicaciones no necesitan utilizar índices de matriz muy grandes, pero sí necesitan realizar verificaciones de rango. Además, muchos programadores no están entrenados para hacer esta expresión de gimnasia de reordenamiento. 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 tratar 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 meterse con las opciones del compilador. Por esta razón, C # favorece el uso de enteros con signo. Cuando estas decisiones se consideran en conjunto, 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 con signo elimine una posible violación del "principio de menos asombro", un ejemplo de ello es OpenCV, que utiliza un entero con signo de 32 bits para el índice del elemento de la matriz, el tamaño de la matriz, el recuento de canales de píxeles, El procesamiento de imágenes es un ejemplo de dominio de programación que usa mucho el índice de matriz relativa. El desbordamiento de enteros sin firmar (el resultado negativo se ajusta) complicará severamente la implementación del algoritmo.

    
respondido por el rwong 14.12.2016 - 05:57
fuente
14

Esta respuesta realmente depende de quién usará tu código y qué estándares quieren ver.

size_t es un tamaño entero con un propósito:

  

El tipo size_t es un tipo entero sin signo definido por la implementación que es lo suficientemente grande como para contener el tamaño en bytes de cualquier objeto. (C ++ 11 especificación 18.2.6)

Por lo tanto, cada vez que desee trabajar con el tamaño de los objetos en bytes, debería usar size_t . Ahora, en muchos casos, no estás utilizando estas dimensiones / índices para contar bytes, pero la mayoría de los desarrolladores optan por usar size_t allí para mantener la coherencia.

Tenga en cuenta que debe siempre usar size_t si su clase pretende tener el aspecto de una clase STL. Todas las clases STL en la especificación usan size_t . Es válido que el compilador escriba typedef size_t sea unsigned int , y también es válido para que se tipifique como unsigned long . Si usa int o long directamente, eventualmente encontrará compiladores donde una persona que cree que su clase siguió el estilo de la STL queda atrapada porque no siguió el estándar.

En cuanto al uso de tipos firmados, hay algunas ventajas:

  • Nombres más cortos: es muy fácil para las personas escribir int , pero mucho más difícil de saturar el código con unsigned int .
  • Un entero para cada tamaño: solo hay un entero compatible con CLS de 32 bits, que es Int32. En C ++, hay dos ( int32_t y uint32_t ). Esto puede simplificar la interoperabilidad de la API

La gran desventaja de los tipos firmados es la obvia: pierdes la mitad de tu dominio. Un número firmado no puede contar tan alto como un número sin firmar. Cuando llegó C / C ++, esto fue muy importante. Uno necesitaba poder abordar toda la capacidad del procesador y, para hacerlo, necesitaba usar números sin firma.

Para los tipos de aplicaciones .NET dirigidas, no era tan necesaria la necesidad de un índice sin signo de dominio completo. Muchos de los propósitos de dichos números simplemente no son válidos en un idioma administrado (la agrupación de memoria viene a la mente). 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 no es tan doloroso como lo era antes. Si realmente necesita 4 mil millones de índices, simplemente cambie a usar enteros de 64 bits. En el peor de los casos, lo ejecutas en una máquina de 32 bits y es un poco lento.

Veo el comercio como uno de conveniencia. Si tiene la suficiente capacidad informática que no le importa perder un poco de su tipo de índice que nunca usará, entonces es conveniente simplemente escribir int o long y alejarse de él. Si encuentra que realmente deseaba ese último bit, entonces probablemente debería haber prestado atención a la firmeza de sus números.

    
respondido por el Cort Ammon 13.12.2016 - 22:45
fuente
4

Creo que la respuesta del rwong anterior ya destaca de manera excelente los problemas.

Añadiré mi 002:

  • size_t , es decir, un tamaño que ...

      

    puede almacenar el tamaño máximo de un objeto teóricamente posible de cualquier tipo (incluida la matriz).

    ... solo es necesario para los índices de rango cuando sizeof(type)==1 , es decir, si está tratando con tipos de bytes ( char ). (Pero, notamos, puede ser más pequeño que un tipo ptr :

  • Como tal, xxx::size_type podría usarse en el 99.9% de los casos, incluso si fuera un tipo de tamaño firmado. (compare ssize_t )
  • El hecho de que std::vector y sus amigos hayan elegido size_t , un tipo sin signo , para el tamaño y la indexación es considerado por algunos como un defecto de diseño. Estoy de acuerdo. (En serio, tómese 5 minutos y vea la rayo charla CppCon 2016: Jon Kalb "sin firmar: Una guía para un mejor código" .)
  • Cuando diseñas una API de C ++ hoy, estás en un lugar difícil: usa size_t para ser consistente con la Biblioteca estándar, o usa (a firmado ) intptr_t o ssize_t para cálculos de indexación fáciles y menos propensos a errores.
  • No use int32 o int64: use intptr_t si desea firmar, y desea el tamaño de palabra de máquina, o use ssize_t .

Para responder directamente a la pregunta, no es completamente un "artefacto histórico", ya que el problema teórico de la necesidad de abordar más de la mitad ("indexación" o) espacio de direcciones debe ser, aehm, abordado de alguna manera en un lenguaje de bajo nivel como C ++.

En retrospectiva, yo, personalmente , creo que es un defecto de diseño que la Biblioteca Estándar utiliza size_t sin firmar en todo el lugar, incluso cuando no representa un tamaño de memoria sin formato, pero una capacidad de datos escritos, como para las colecciones:

  • dadas las reglas de promoción de enteros de C ++ - >
  • los tipos sin firma simplemente no son buenos candidatos para los tipos "semánticos" para algo como un tamaño que no tiene firma semántica.

Repetiré el consejo de Jon aquí:

  • Seleccione los tipos para las operaciones que admiten (no el rango de valores). (* 1)
  • No uses tipos sin firmar en tu API. Esto oculta errores sin beneficio al alza.
  • No use "unsigned" para cantidades. (* 2)

(* 1) es decir, sin signo == máscara de bits, nunca haga cálculos matemáticos (en este caso se encuentra la primera excepción: es posible que necesite un contador que contenga este tipo de caracteres).

(* 2) cantidades que significan algo con lo que cuenta y / o hace matemáticas.

    
respondido por el Martin Ba 14.12.2016 - 22:44
fuente
0

Simplemente agregaré que, por razones de rendimiento, normalmente uso size_t, para garantizar que los cálculos erróneos causen un flujo insuficiente, lo que significa que ambas verificaciones de rango (por debajo de cero y por encima del tamaño ()) se pueden reducir a una :

utilizando int firmado:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

utilizando int sin firmar:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}
    
respondido por el asger 20.12.2016 - 13:40
fuente

Lea otras preguntas en las etiquetas