0

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
}
John Lock
  • 9
  • 2
  • 4
    I'm not sure how you can make sense of your second example. What's supposed to happen if `health > MAX_OF_INT16`? Is this impossible? Then make `health` an `int16`. Do you want to truncate the value? Then make it explicit. The second code just screams "I don't know what I'm doing". – Vincent Savard May 31 '16 at 16:16
  • Some people use just plain int, even when its impossible for variable to have negative value, or value that high. And I would use int also as default. Im truncating it only when saving it to object member, only for purpose of memory saving. – John Lock May 31 '16 at 16:31
  • 1
    It is still a very surprising behaviour. If I see a function that accepts an `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. – Vincent Savard May 31 '16 at 16:34
  • This question addresses another issue with the code, passing around data members not wrapped in their object: [Do you generally send objects or their member variables into functions?](http://programmers.stackexchange.com/q/319376/) –  Jun 01 '16 at 05:04

1 Answers1

6

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 ints. 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.

Nicol Bolas
  • 11,813
  • 4
  • 37
  • 46
  • 2
    In 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. +1 – Mike May 31 '16 at 20:50