Jusqu'où aller avec les types primitifs typés comme int

14

J'ai vu du code C ++ tel que le suivant avec de nombreux typedefs.

Quels sont les avantages d'utiliser de nombreux typedefs comme celui-ci par rapport à l'utilisation de primitives C ++? Existe-t-il une autre approche qui pourrait également générer ces avantages?

Au final, les données sont toutes stockées en mémoire ou transmises sur le fil sous forme de bits et d'octets, est-ce vraiment important?

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;

Il semble logique d'essayer de s'assurer que le même type est toujours utilisé pour une chose particulière pour éviter les débordements, mais je vois souvent du code où "int" vient d'être utilisé à peu près partout à la place. L' typedefing mène souvent à un code qui ressemble un peu à ceci:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

Ce qui me fait alors me demander quel jeton décrit réellement le paramètre: le type de paramètre ou le nom du paramètre?

marque
la source
2
À mon humble avis, cela semble être un exercice assez inutile, mais je suis sûr que d'autres ne seraient pas d'accord ...
Nim
1
Ces types ressemblent à des noms de variables.
Captain Giraffe
11
Notez que cela donne l'impression qu'il est sûr pour le type, mais pas du tout - les typedefs créent simplement des alias, mais rien ne vous empêche de passer par exemple un Minuteà une fonction qui a un argument déclaré comme type Second.
Jesper
2
@Mark: regardez-le d'une autre manière. Si vous faites une erreur en décidant le type entier, ou si de nouvelles exigences émergent à l'avenir, et si vous voulez le changer, voulez-vous changer un seul typedef ou voulez-vous rechercher le code pour chaque fonction qui manipule une année, et changer sa signature? 640k est suffisant pour tout le monde, et tout ça. L'inconvénient correspondant du typedef est que les gens écrivent accidentellement ou délibérément du code qui repose sur le fait que Year est exactement 16 bits, puis il change et leur code se casse.
Steve Jessop
1
@Steve Jessop: Je ne peux pas décider si vous pensez que c'est une bonne ou une mauvaise idée :-) La première partie semble être en faveur, la seconde contre. Je suppose que cela a des avantages et des inconvénients.

Réponses:

13

Le nom d'un paramètre doit décrire ce qu'il signifie - dans votre cas, le niveau d'escalade. Le type est la façon dont la valeur est représentée - l'ajout de typedefs comme dans votre exemple masque cette partie de la signature de fonction, donc je ne le recommanderais pas.

Les typedefs sont utiles pour les modèles ou si vous souhaitez modifier le type utilisé pour certains paramètres, par exemple lors de la migration d'une plate-forme 32 bits vers une plate-forme 64 bits.

Björn Pollex
la source
Cela semble alors être le consensus général. Donc, vous en tiendriez-vous généralement à "int"? Je dois être très efficace dans cette application en ce qui concerne le transfert de données, alors serait-ce juste une conversion en int16_t (ou quel que soit le plus grand type de représentation nécessaire pour un élément particulier) pendant la sérialisation?
@Mark: Oui, c'est comme ça que tu devrais le faire. Utilisez des typedefs pour exprimer la taille des types de données utilisés, mais ne différenciez pas le même type utilisé dans des contextes différents.
Björn Pollex
Merci - et juste pour clarifier, vous ne prendriez pas la peine d'utiliser int8_t au lieu de int généralement dans le code .. Je suppose que ma principale préoccupation était quelque chose comme "Identité" qui est en fait une identité générée par une base de données. Pour le moment, c'est 32 bits, mais je ne sais pas encore si cela pourrait éventuellement devenir 64 bits. Aussi, que diriez-vous int32_t vs int? int est généralement le même que int32_t, mais il se peut que ce ne soit pas toujours le cas sur une plate-forme différente? Je pense que je devrais simplement m'en tenir à "int" en général, et à "int64_t" où c'est nécessaire .. merci :-)
@Mark: L'important à propos des typedefs comme int32_tc'est que vous devez vous assurer qu'ils sont corrects lors de la compilation sur différentes plateformes. Si vous vous attendez à ce que la plage de Identitychange à un moment donné, je pense que je préférerais apporter les modifications directement dans tout le code affecté. Mais je ne suis pas sûr, car j'aurais besoin d'en savoir plus sur votre conception spécifique. Vous voudrez peut-être en faire une question distincte.
Björn Pollex
17

Au début, j'ai pensé "Pourquoi pas", mais il m'est venu à l'esprit que si vous allez à des longueurs telles pour séparer les types comme ça, alors faites un meilleur usage de la langue. Au lieu d'utiliser des alias, définissez en fait des types:

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

Il n'y a pas de différence de performances entre:

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

et:

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

et vous avez également l'avantage d'ajouter la validation des paramètres et la sécurité des types. Par exemple, considérons le code qui traite de l'argent en utilisant des types primitifs:

float amount = 10.00;
CallSomeFunction(amount);

Outre les problèmes d'arrondi, il autorise également tout type pouvant être converti en flottant:

int amount = 10;
CallSomeFunction(amount);

Dans ce cas, ce n'est pas grave, mais les conversions implicites peuvent être une source de bogues difficiles à cerner. L'utilisation d'un typedefn'aide pas ici, car il s'agit simplement d'un alias de type.

L'utilisation d'un nouveau type signifie qu'il n'y a pas de conversions implicites à moins que vous ne codiez un opérateur de transtypage, ce qui est une mauvaise idée précisément parce qu'il autorise les conversions implicites. Vous pouvez également encapsuler des données supplémentaires:

class Money {
  Decimal amount;
  Currency currency;
};

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

Rien d'autre ne rentrera dans cette fonction à moins que nous écrivions du code pour y arriver. Les conversions accidentelles sont impossibles. Nous pouvons également écrire des types plus complexes au besoin sans trop de tracas.

Skizz
la source
1
Vous pouvez même écrire une macro horrible pour faire toute cette création de classe pour vous. (Allez, Skizz. Tu sais que tu le veux.)
1
Pour certains d'entre eux, une bibliothèque d'unités de type sécurisé peut être utile ( tuoml.sourceforge.net/html/scalar/scalar.html ), plutôt que d'écrire une classe personnalisée pour chacun.
Steve Jessop
@Chris - Certainement pas une macro, mais peut-être une classe de modèle. Comme le souligne Steve, ces cours sont déjà écrits.
kevin cline
4
@Chris: La macro s'appelle en BOOST_STRONG_TYPEDEFfait;)
Matthieu M.
3

L'utilisation de typedefs pour des types primitifs comme celui-ci ressemble plus à du code de style C.

En C ++, vous obtiendrez des erreurs intéressantes dès que vous essayez de surcharger des fonctions pour, disons, EventLevelet Hour. Cela rend les noms de type supplémentaires plutôt inutiles.

Bo Persson
la source
2

Nous (dans notre entreprise) le faisons beaucoup en C ++. Il aide à comprendre et à maintenir le code. Ce qui est bien pour déplacer des personnes entre des équipes ou pour refactoriser. Exemple:

typedef float Price;
typedef int64_t JavaTimestmap;

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

Nous pensons que c'est une bonne pratique de créer typedef pour le nom de dimension à partir du type de représentation. Ce nouveau nom représente un rôle général dans un logiciel. Le nom d'un paramètre est un rôle local . Comme dans User sender, User receiver. À certains endroits, cela peut être redondant, void register(User user)mais je ne pense pas que ce soit un problème.

Plus tard, on peut avoir l'idée que ce floatn'est pas le meilleur pour représenter les prix en raison des règles d'arrondi spéciales de la réservation, donc on télécharge ou implémente un type BCDFloat(décimal codé binaire) et change le typedef. Il n'y a pas de recherche et de remplacer le travail de floatà BCDFloatqui serait durci par le fait qu'il ya peut - être beaucoup plus flotteurs dans votre code.

Ce n'est pas une solution miracle et a ses propres mises en garde, mais nous pensons qu'il est beaucoup mieux de l'utiliser que non.

Pas dans la liste
la source
Ou comme Mathieu M. l'a suggéré au poste de Skizz, on peut aller comme ça BOOST_STRONG_TYPEDEF(float, Price), mais je n'irais pas aussi loin sur un projet moyen. Ou peut-être que je le ferais. Je dois dormir dessus. :-)
Notinlist
1

typedefvous permet essentiellement de donner un alias pour un type.
Il vous donne la possibilité d'éviter de taper le long type namesencore et encore et de rendre votre typetexte plus facilement lisible dans lequel le nom d'alias indique l'intention ou le but du type.

C'est plus une question de choix si vous souhaitez avoir des noms plus lisibles typedefdans votre projet.
Habituellement, j'évite d'utiliser typedefdes types primitifs, à moins qu'ils ne soient inhabituellement longs à taper. Je garde mes noms de paramètres plus indicatifs.

Alok Save
la source
0

Je ne ferais jamais quelque chose comme ça. S'assurer qu'ils ont tous la même taille est une chose, mais il vous suffit de les désigner comme des types intégraux.

DeadMG
la source
0

L'utilisation de typedefs comme celui-ci est acceptable tant que celui qui les utilise n'a pas besoin de savoir quoi que ce soit sur leur représentation sous-jacente . Par exemple, si vous souhaitez passer un PacketLengthobjet à l'un printfou à l'autre scanf, vous devrez connaître son type réel afin de pouvoir choisir le bon spécificateur de conversion. Dans de tels cas, le typedef ajoute juste un niveau d'obscurcissement sans rien acheter en retour; vous pourriez aussi bien avoir défini l'objet comme int32_t.

Si vous devez appliquer une sémantique spécifique à chaque type (comme des plages ou des valeurs autorisées), vous feriez mieux de créer un type de données abstrait et des fonctions pour opérer sur ce type, plutôt que de simplement créer un typedef.

John Bode
la source