Stai facendo la domanda sbagliata.
La domanda giusta è "chi dovrebbe essere responsabile di garantire che i valori desiderati siano entro i limiti richiesti dal tipo di dati?"
Diamo un'occhiata ai tuoi casi:
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
}
L'interfaccia di function1
ha un requisito di dimensioni esplicito sui suoi tipi. È quindi responsabilità di tutti che chiama tale funzione per verificare che i valori che si desidera memorizzare rientrino nelle dimensioni specificate nell'interfaccia.
function2
ha invece un requisito di dimensioni implicito . Dice che ci vuole int
s. Ma non è così; se passi valori al di fuori della dimensione del tipo di dati, invoca il comportamento definito dall'implementazione.
Per certi aspetti, function1
è migliore. Le dimensioni per i parametri sono esplicitamente indicate. Ma a causa delle regole di conversione di C ++, qualcosa ovviamente rotto come questo non causerà nemmeno un errore di compilazione:
function1(0xFFFFFFF, 23);
Questa è una conversione restringente, che è consentita. Richiama il comportamento definito dall'implementazione, che probabilmente non è quello che desideri.
Quindi, la vera domanda è ... che fai vuoi?
Se l'utente desidera impostare lo stato su più di 0x7FFF, che cosa dovrebbe fare il codice? Dovrebbe accettarlo silenziosamente e invocare un comportamento definito dall'implementazione? O dovrebbe provocare una condizione di errore, lanciare un'eccezione o terminare semplicemente?
Se vuoi fare qualsiasi di quest'ultimo, devi usare function2
(o meglio, una versione di controllo degli errori di function2
). Usandolo, il requisito implicito dell'interfaccia può essere almeno verificato. Ciò rende possibile rintracciare il codice che vuole impostare il valore in modo improprio.