17

I have seen C++ code such as the following with many typedefs.

What are the benefits of using many typedefs like this as opposed to using C++ primitives? Is there another approach that might also achieve those benefits?

In the end, the data is all stored in memory or transmitted over the wire as bits and bytes, does it really matter?

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;

It seems logical to try and make sure the same type is always used for a particular thing to avoid overflows, but I do often see code where "int" has just been used pretty much everywhere instead. The typedefing often does lead to code that looks a bit like this though:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

Which then makes me wonder which token is actually describing the parameter: the parameter type or the parameter name?

11
  • 2
    IMHO, seems like a pretty pointless exercise, but I'm sure some others would disagree...
    – Nim
    Commented Jun 20, 2011 at 10:03
  • 1
    Those types look like variable names. Commented Jun 20, 2011 at 10:05
  • 11
    Note that this creates the impression that it's type-safe, but it not at all - the typedefs just create aliases, but nothing stops you from passing for example a Minute to a function that has an argument declared as type Second.
    – Jesper
    Commented Jun 20, 2011 at 10:06
  • 2
    @Mark: look at it another way. If you make a mistake deciding the integer type, or new requirements emerge in future, and so you want to change it, would you like to change a single typedef or would you like to search the code for every function that manipulates a year, and change its signature? 640k is enough for anyone, and all that. The corresponding downside to the typedef is that people accidentally or deliberately write code that relies on the fact that Year is exactly 16 bits, then it changes and their code breaks. Commented Jun 20, 2011 at 10:53
  • 1
    @Steve Jessop: I can't decide if you think it's a good or bad idea :-) The first part seems to be in favour, the latter against. I guess it has pros and cons then.
    – Mark
    Commented Jun 20, 2011 at 11:11

7 Answers 7

14

The name of a parameter should describe what it means - in your case the escalation level. The type is how the value is represented - adding typedefs as in your example obfuscates this part of the function-signature, so I would not recommend it.

Typedefs are useful for templates, or if you want to change the type used for certain parameters, for instance when migrating from a 32bit to a 64bit platform.

4
  • This seems to be the general consensus then. So would you generally just stick to "int"? I have to be very efficient in this app when it comes to transfering data, so would it be a case of just convert to int16_t (or whatever the largest representation type needed for a particular element) during serialization?
    – Mark
    Commented Jun 20, 2011 at 10:54
  • @Mark: Yes, that is how you should do it. Use typedefs to express the size of the used data-types, but do not differentiate the same type used in different contexts. Commented Jun 20, 2011 at 10:56
  • Thanks - and just to clarify, you wouldn't bother using int8_t instead of int generally in the code.. I suppose my main worry was something like "Identity" which is actually a identity generated by a database. At the moment it is 32bit but I'm not sure yet if that may eventually become 64bit. Also, how about int32_t vs int? int is usually the same as int32_t, but it might not always be I guess on a different platform? I'm thinking I should just stick to "int" generally, and "int64_t" where neccessary.. thanks :-)
    – Mark
    Commented Jun 20, 2011 at 11:03
  • @Mark: The important thing about typedefs like int32_t is that you have to make sure they are correct when compiling on different platforms. If you expect the range of Identity to change at some point, I think I would prefer to make the changes directly in all affected code. But I am not sure, because I would need to know more about your specific design. You might want to make that a separate question. Commented Jun 20, 2011 at 11:09
17

At first I thought "Why not" but then it occurred to me that if you're going to go to such lengths to separate the types like that, then make better use of the language. Instead of using aliases, actually define types:

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

There is no performance difference between:

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

and:

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

and you also have the advantages of adding parameter validation and type safety. For example, consider code that deals with money using primitive types:

float amount = 10.00;
CallSomeFunction(amount);

Aside from the rounding issues, it also allows any type that can be converted to a float:

int amount = 10;
CallSomeFunction(amount);

In this case it is not a big deal, but implicit conversions can be a source of bugs that is difficult to pin down. Using a typedef does not help here, since they are merely a type alias.

Using a new type entirely means there are no implicit conversions unless you code a casting operator, which is a bad idea specifically because it allows implicit conversions. You can also encapsulate additional data:

class Money {
  Decimal amount;
  Currency currency;
};

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

Nothing else will fit into that function unless we write code to make it happen. Accidental conversions are impossible. We can also write more complex types as needed without much hassle.

4
  • 1
    You could even write some horrible macro to do all of this class creation for you. (Come on, Skizz. You know you want to.)
    – Chris Lutz
    Commented Jun 20, 2011 at 10:22
  • 1
    For some of these, a type-safe units library might be helpful (tuoml.sourceforge.net/html/scalar/scalar.html), rather than writing a custom class for each. Commented Jun 20, 2011 at 10:56
  • @Chris - Definitely not a macro, but possibly a template class. As Steve points out, those classes are already written. Commented Jun 20, 2011 at 13:31
  • 4
    @Chris: The macro is called BOOST_STRONG_TYPEDEF actually ;) Commented Jun 20, 2011 at 18:15
4

Using typedefs for primitive types like that looks more like C style code.

In C++ you will get interesting errors as soon as you try to overload functions for, say, EventLevel and Hour. That makes the extra type names pretty useless.

3

We (at our company) do it a lot in C++. It helps understanding and maintaining code. Which is good when moving people between teams or doing refactoring. Example:

typedef float Price;
typedef int64_t JavaTimestmap;

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

We believe it's a good practice to create typedef to the dimension name from the representation type. This new name represents a general role in a software. The name of a parameter is a local role. Like in User sender, User receiver. At some places it can be redundant, like void register(User user), but I don't consider it being a problem.

Later one may have the idea that float is not the best for representing prices because of the special rounding rules of booking, so one downloads or implements a BCDFloat (binary coded decimal) type and changes the typedef. There is no search and replace work from float to BCDFloat which would be hardened by the fact that there are possibly many more floats in your code.

It is not a silver bullet and has its own caveats, but we think it is much better using it than not.

1
  • Or as Mathieu M. suggested at the post of Skizz one can go like BOOST_STRONG_TYPEDEF(float, Price), but I wouldn't go that far on an average project. Or maybe I would. I have to sleep on it. :-)
    – Notinlist
    Commented Oct 13, 2015 at 15:19
1

typedef basically allows you to give an alias for an type.
It gives you the flexibility of avoiding typing the long type names again and again and making your type more easily readable wherein the alias name indicates the intent or purpose of the type.

It's more of an matter of choice if you wish to have more readable names through typedef in your project.
Usually, I avoid using typedef on primitive types, unless they are unusually long to be typed. I keep my parameter names more indicative.

1

Using typedefs like this is okay as long as whoever winds up using them doesn't need to know anything about their underlying representation. For example, if you want to pass a PacketLength object to either printf or scanf, you'll need to know its actual type so you can pick the right conversion specifier. In cases like that, the typedef just adds a level of obfuscation without buying anything in return; you might as well have just defined the object as int32_t.

If you need to enforce semantics specific to each type (such as allowable ranges or values), then you're better off creating an abstract data type and functions to operate on that type, rather than just creating a typedef.

0

I would never do something like this. Making sure they all have the same size is one thing- but you only need to refer to them as integral types, then.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.