I often use smaller data types in structs/classes when it is needed in memory savings. I also use them in network/disk IO.
My question is if intermediate code that use those objects with smaller data types, should be of the same type, or just plain types like int and cut them when storing. For example:
struct Object
{
int16 health;
int8 mana;
};
void function(int16 health, int8 mana)
{
Object object;
object.health = health;
object.mana = mana;
}
or
void function(int health, int mana)
{
Object object;
object.health = static_cast<int16>(health); // Cutting int
object.mana = static_cast<int8>(mana); // Cutting int
}
1 Answer 1
You're asking the wrong question.
The right question is "who should be responsible for ensuring that the desired values are within the limitations required by the data type?"
Let's look at your cases:
void function1(int16_t health, int8_t mana)
{
Object object;
object.health = health;
object.mana = mana;
}
void function2(int health, int mana)
{
Object object;
object.health = static_cast<int16_t>(health); // Cutting int
object.mana = static_cast<int8_t>(mana); // Cutting int
}
The interface of function1
has an explicit size requirement on its types. It is therefore the responsibility of everyone who calls that function to verify that the values that they want to store fit within the sizes specified in the interface.
function2
by contrast has an implicit size requirement. It says that it takes int
s. But it doesn't really; if you pass it values outside of the size of the data type, then it invokes implementation-defined behavior.
In some respects, function1
is better. The sizes for the parameters are explicitly stated. But because of C++'s conversion rules, something as obviously broken as this won't cause even a compile error:
function1(0xFFFFFFF, 23);
That is a narrowing conversion, which is allowed. It invokes implementation-defined behavior, which is probably not what you want.
Thus, the real question is... what do you want?
If the user wants to set the health to more than 0x7FFF, what should your code do? Should it silently accept it and invoke implementation-defined behavior? Or should it provoke an error condition, throw an exception or just flat-out terminate?
If you want to do any of the latter, then you must use function2
(or rather, an error-checking version of function2
). By using it, the implicit interface requirement can at least be verified. This makes it possible to track down code that wants to set the value improperly.
-
2In my experience "Should it silently accept it and invoke implementation-defined behavior?" is almost always wrong. "Or should it provoke an error condition, throw an exception or just flat-out terminate?" is almost always right. That being said, every situation should be judged on its own merits. +1Mike– Mike2016年05月31日 20:50:18 +00:00Commented May 31, 2016 at 20:50
health > MAX_OF_INT16
? Is this impossible? Then makehealth
anint16
. Do you want to truncate the value? Then make it explicit. The second code just screams "I don't know what I'm doing".int
, then I assume I can safely pass it an int. Your function would just do a half-assed attempt to cast it to the correct type, and it would most likely return garbage for anything not in the range of an int16 or int8. I don't see any upside for your second code.