Eu uso ints não assinados para tornar meu código e sua intenção mais claros. Uma coisa que faço para me proteger contra conversões implícitas inesperadas ao fazer aritmética com tipos assinados e não assinados é usar um short não assinado (normalmente 2 bytes) para minhas variáveis não assinadas. Isso é eficaz por algumas razões:
- Quando você faz aritmética com suas variáveis curtas não assinadas e literais (que são do tipo int) ou variáveis do tipo int, isso garante que a variável não assinada sempre será promovida a um int antes de avaliar a expressão, pois int sempre tem uma maior rank do que curto. Isso evita qualquer comportamento inesperado que faça aritmética com tipos assinados e não assinados, assumindo que o resultado da expressão se encaixa em um int assinado, é claro.
- Na maior parte do tempo, as variáveis não assinadas que você está usando não excederão o valor máximo de um byte de 2 bytes curto (65.535)
O princípio geral é que o tipo de variáveis não assinadas deve ter uma classificação mais baixa do que o tipo das variáveis assinadas para garantir a promoção ao tipo assinado. Então você não terá nenhum comportamento inesperado de estouro. Obviamente, você não pode garantir isso o tempo todo, mas (a maioria) é viável para garantir isso.
Por exemplo, recentemente eu tive algum loop para algo assim:
const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
if((i-2)%cuint == 0)
{
//Do something
}
}
O literal '2' é do tipo int. Se eu fosse um int não assinado em vez de um curto não assinado, então na subexpressão (i-2), 2 seria promovido para um int não assinado (já que unsigned int tem uma prioridade mais alta que o int assinado). Se i = 0, então a sub-expressão é igual a (0u-2u) = algum valor massivo devido ao estouro. Mesma idéia com i = 1. No entanto, como i é um curto não assinado, ele é promovido para o mesmo tipo do literal '2', que é assinado int e tudo funciona bem.
Para segurança adicional: no caso raro em que a arquitetura que você está implementando faz com que int seja de 2 bytes, isso pode fazer com que ambos os operandos na expressão aritmética sejam promovidos para unsigned int no caso em que a variável curta não assinada não se encaixa no int de 2 bytes assinado, o último dos quais tem um valor máximo de 32.767 < 65,535. (Veja link para mais detalhes). Para proteger-se contra isso, você pode simplesmente adicionar um static_assert ao seu programa da seguinte forma:
static_assert(sizeof(int) == 4, "int must be 4 bytes");
e não irá compilar em arquiteturas onde int é de 2 bytes.