Qué tan lejos ir con typedef'ing tipos primitivos como int

14

He visto código C ++ como el siguiente con muchos typedefs.

¿Cuáles son los beneficios de usar muchos typedefs como este en lugar de usar primitivas de C ++? ¿Existe otro enfoque que también pueda lograr esos beneficios?

Al final, todos los datos se almacenan en la memoria o se transmiten a través del cable como bits y bytes, ¿realmente importa?

types.h:

typedef int16_t Version;
typedef int32_t PacketLength;
typedef int32_t Identity;
typedef int32_t CabinetNumber;
typedef int64_t Time64;
typedef int64_t RFID;
typedef int64_t NetworkAddress;
typedef int64_t PathfinderAddress;
typedef int16_t PathfinderPan;
typedef int16_t PathfinderChannel;
typedef int64_t HandsetSerialNumber;
typedef int16_t PinNumber;
typedef int16_t LoggingInterval;
typedef int16_t DelayMinutes;
typedef int16_t ReminderDelayMinutes;
typedef int16_t EscalationDelayMinutes;
typedef float CalibrationOffset;
typedef float AnalogValue;
typedef int8_t PathfinderEtrx;
typedef int8_t DampingFactor;
typedef int8_t RankNumber;
typedef int8_t SlavePort;
typedef int8_t EventLevel;
typedef int8_t Percent;
typedef int8_t SensorNumber;
typedef int8_t RoleCode;
typedef int8_t Hour;
typedef int8_t Minute;
typedef int8_t Second;
typedef int8_t Day;
typedef int8_t Month;
typedef int16_t Year;
typedef int8_t EscalationLevel;

Parece lógico tratar de asegurarme de que siempre se use el mismo tipo para algo en particular para evitar desbordamientos, pero a menudo veo código donde "int" se ha usado prácticamente en todas partes. Sin typedefembargo, a menudo el código conduce a un código que se parece un poco a esto:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

¿Qué me hace preguntarme qué token describe realmente el parámetro: el tipo de parámetro o el nombre del parámetro?

marca
fuente
2
En mi humilde opinión, parece un ejercicio bastante inútil, pero estoy seguro de que algunos otros estarían en desacuerdo ...
Nim
1
Esos tipos parecen nombres de variables.
Captain Giraffe
11
Tenga en cuenta que esto crea la impresión de que es de tipo seguro, pero no lo es en absoluto: los typedefs solo crean alias, pero nada le impide pasar, por ejemplo, Minutea una función que tiene un argumento declarado como tipo Second.
Jesper
2
@ Mark: míralo de otra manera. Si comete un error al decidir el tipo entero, o si surgen nuevos requisitos en el futuro, y desea cambiarlo, ¿le gustaría cambiar un solo tipo de definición o le gustaría buscar el código para cada función que manipula un año, y cambiar su firma? 640k es suficiente para cualquiera, y todo eso. La desventaja correspondiente al typedef es que las personas escriben código accidental o deliberadamente que se basa en el hecho de que Year tiene exactamente 16 bits, luego cambia y su código se rompe.
Steve Jessop
1
@Steve Jessop: No puedo decidir si crees que es una buena o mala idea :-) La primera parte parece estar a favor, la segunda en contra. Supongo que tiene pros y contras entonces.

Respuestas:

13

El nombre de un parámetro debe describir lo que significa, en su caso, el nivel de escalamiento. El tipo es cómo se representa el valor: agregar typedefs como en su ejemplo ofusca esta parte de la firma de la función, por lo que no lo recomendaría.

Los typedefs son útiles para las plantillas, o si desea cambiar el tipo utilizado para ciertos parámetros, por ejemplo, al migrar de una plataforma de 32 bits a una de 64 bits.

Björn Pollex
fuente
Este parece ser el consenso general entonces. Entonces, ¿generalmente se apegaría a "int"? Tengo que ser muy eficiente en esta aplicación cuando se trata de transferir datos, ¿sería un caso de simplemente convertir a int16_t (o el tipo de representación más grande necesario para un elemento en particular) durante la serialización?
@ Mark: Sí, así es como debes hacerlo. Utilice typedefs para expresar el tamaño de los tipos de datos utilizados, pero no diferencie el mismo tipo utilizado en diferentes contextos.
Björn Pollex
Gracias, y solo para aclarar, no se molestaría en usar int8_t en lugar de int en general en el código. Supongo que mi principal preocupación era algo así como "Identidad", que en realidad es una identidad generada por una base de datos. Por el momento es de 32 bits, pero todavía no estoy seguro de si eso puede llegar a ser de 64 bits. Además, ¿qué tal int32_t vs int? int suele ser lo mismo que int32_t, pero ¿no podría ser siempre en una plataforma diferente? Estoy pensando que debería apegarme a "int" en general, y "int64_t" donde sea necesario ... gracias :-)
@ Mark: Lo importante de los typedefs int32_tes que debes asegurarte de que sean correctos al compilar en diferentes plataformas. Si espera que el rango Identitycambie en algún momento, creo que preferiría hacer los cambios directamente en todo el código afectado. Pero no estoy seguro, porque necesitaría saber más sobre su diseño específico. Es posible que desee hacer eso una pregunta por separado.
Björn Pollex
17

Al principio pensé "¿Por qué no?", Pero luego se me ocurrió que si ibas a hacer todo lo posible para separar los tipos como ese, entonces harías un mejor uso del lenguaje. En lugar de usar alias, defina los tipos:

class AnalogueValue
{
public:
    // constructors, setters, getters, etc..
private:
    float m_value;
};

No hay diferencia de rendimiento entre:

typedef float AnalogueValue;
AnalogValue a = 3.0f;
CallSomeFunction (a);

y:

AnalogValue a (3.0f); // class version
CallSomeFunction (a);

y también tiene las ventajas de agregar validación de parámetros y seguridad de tipos. Por ejemplo, considere el código que trata con dinero usando tipos primitivos:

float amount = 10.00;
CallSomeFunction(amount);

Además de los problemas de redondeo, también permite cualquier tipo que se pueda convertir en flotante:

int amount = 10;
CallSomeFunction(amount);

En este caso no es un gran problema, pero las conversiones implícitas pueden ser una fuente de errores difíciles de precisar. El uso de a typedefno ayuda aquí, ya que son simplemente un alias de tipo.

Usar un nuevo tipo completamente significa que no hay conversiones implícitas a menos que codifique un operador de conversión, lo cual es una mala idea específicamente porque permite conversiones implícitas. También puede encapsular datos adicionales:

class Money {
  Decimal amount;
  Currency currency;
};

Money m(Decimal("10.00"), Currency.USD);
CallSomeFunction(m);

Nada más encajará en esa función a menos que escribamos código para que suceda. Las conversiones accidentales son imposibles. También podemos escribir tipos más complejos según sea necesario sin mucha molestia.

Skizz
fuente
1
Incluso podrías escribir alguna macro horrible para hacer toda esta creación de clase por ti. (Vamos, Skizz. Sabes que quieres)
1
Para algunos de estos, una biblioteca de unidades de tipo seguro puede ser útil ( tuoml.sourceforge.net/html/scalar/scalar.html ), en lugar de escribir una clase personalizada para cada uno.
Steve Jessop
@ Chris - Definitivamente no es una macro, sino posiblemente una clase de plantilla. Como señala Steve, esas clases ya están escritas.
Kevin Cline
44
@ Chris: La macro se llama en BOOST_STRONG_TYPEDEFrealidad;)
Matthieu M.
3

El uso de typedefs para tipos primitivos como ese se parece más al código de estilo C.

En C ++ obtendrá errores interesantes tan pronto como intente sobrecargar las funciones para, digamos, EventLevely Hour. Eso hace que los nombres de tipo adicionales sean bastante inútiles.

Bo Persson
fuente
2

Nosotros (en nuestra empresa) lo hacemos mucho en C ++. Ayuda a comprender y mantener el código. Lo cual es bueno cuando mueves personas entre equipos o realizas refactorizaciones. Ejemplo:

typedef float Price;
typedef int64_t JavaTimestmap;

void f(JavaTimestamp begin, JavaTimestamp end, Price income);

Creemos que es una buena práctica crear typedef para el nombre de dimensión desde el tipo de representación. Este nuevo nombre representa un rol general en un software. El nombre de un parámetro es un rol local . Al igual que en User sender, User receiver. En algunos lugares puede ser redundante, void register(User user)pero no considero que sea un problema.

Más adelante, uno puede tener la idea de que floatno es lo mejor para representar precios debido a las reglas especiales de redondeo de la reserva, por lo que uno descarga o implementa un tipo BCDFloat(decimal codificado en binario) y cambia la definición de tipo. No hay una búsqueda y reemplazo de trabajo floata BCDFloatque se endurezca por el hecho de que hay posiblemente muchos más flotadores en su código.

No es una bala de plata y tiene sus propias advertencias, pero creemos que es mucho mejor usarlo que no.

No en lista
fuente
O, como Mathieu M. sugirió en la publicación de Skizz, uno puede ir BOOST_STRONG_TYPEDEF(float, Price), pero no llegaría tan lejos en un proyecto promedio. O tal vez lo haría. Tengo que consultarlo con la almohada. :-)
Notinlist
1

typedefbásicamente le permite dar un alias para un type.
Le brinda la flexibilidad de evitar escribir el teclado type namesuna y otra vez y hacer que su typelectura sea más fácil en donde el nombre del alias indica la intención o el propósito del type.

Es más una cuestión de elección si desea tener nombres más legibles typedefen su proyecto.
Por lo general, evito usar typedefen tipos primitivos, a menos que sean demasiado largos para escribirlos. Mantengo mis nombres de parámetros más indicativos.

Alok Save
fuente
0

Nunca haría algo como esto. Asegurarse de que todos tengan el mismo tamaño es una cosa, pero solo debe referirse a ellos como tipos integrales, entonces.

DeadMG
fuente
0

Usar typedefs como este está bien siempre y cuando quien termine de usarlos no necesite saber nada sobre su representación subyacente . Por ejemplo, si desea pasar un PacketLengthobjeto a uno printfo a otro scanf, deberá conocer su tipo real para poder elegir el especificador de conversión correcto. En casos como ese, typedef solo agrega un nivel de ofuscación sin comprar nada a cambio; bien podría haber definido el objeto como int32_t.

Si necesita aplicar una semántica específica para cada tipo (como rangos o valores permitidos), es mejor que cree un tipo de datos abstractos y funciones para operar con ese tipo, en lugar de simplemente crear una definición de tipo.

John Bode
fuente