Recently, during a refactoring session, I was looking over some code I wrote and noticed several things:
unsigned char
to enforce values in the interval [0-255].int
or long
data types with if
statements inside the functions to silently clamp the values to valid ranges.upper bound
but a known and definite non-negative lower bound
were declared as an unsigned
data type (int
or long
depending on the possibility that the upper bound
went above 4,000,000,000).The inconsistency is unnerving. Is this a good practice that I should continue? Should I rethink the logic and stick to using int
or long
with appropriate non-notifying clamping?
A note on the use of "appropriate": There are cases where I use signed
data types and throw notifying exceptions when the values go out of range but these are reserved for divde by zero
and constructors
.
I would probably argue that consistency is most important. If you pick one way and do it right then it will be easy for someone else to understand what you are doing at a later point in time. On the note of doing it right, there are several issues to think about.
First, it is common when checking if an integer variable n is in a valid range, say 0 to N to write:
if ( n > 0 && n <= N ) ...
This comparison only makes sense if n is signed. If n is unsigned then it will never be less than 0 since negative values will wrap around. You could rewrite the above if as just:
if ( n <= N ) ...
If someone isn't used to seeing this, they might be confused and think you did it wrong.
Second, I would keep in mind that there is no guarantee of type size for integers in c++. Thus, if you want something to be bounded by 255, an unsigned char may not do the trick. If the variable has a specific meaning then it may be valuable to to a typedef to show that. For example, size_t is a value as wide as a memory address. Which means that you can use it with arrays and not have to worry about being on 32 or 64 bit machines. I try to use such typedefs whenever possible because they clearly communicate why I am using the type. (size_t because I'm accessing an array.)
Third, is back on the issue of wrap around. What do you want to happen with an invalid number. In the case of an unsigned char, if you use the type to bound the data, then you won't be able to check if a value over 255 was entered. That may or may not be a problem.