Не пытайтесь избежать отрицательных значений с помощью `unsigned`
Причина
Выбор unsigned влечёт за собой множество изменений в обычном поведении целых чисел, включая модульную арифметику, может подавлять предупреждения, связанные с переполнением, и открывает дверь для ошибок, связанных со смешением знаковых и беззнаковых значений. Использование unsigned на самом деле не устраняет возможность отрицательных значений.
Пример
unsigned int u1 = -2; // Допустимо: значение u1 равно 4294967294
int i1 = -2;
unsigned int u2 = i1; // Допустимо: значение u2 равно 4294967294
int i2 = u2; // Допустимо: значение i2 равно -2
Проблемы с такими (совершенно допустимыми) конструкциями сложно заметить в реальном коде, и они являются источником многих реальных ошибок. Рассмотрим:
unsigned area(unsigned height, unsigned width) { return height*width; } // [смотрите также](/roadmap/cpp/guidelines/i-6)
// ...
int height;
cin >> height;
auto a = area(height, 2); // если ввод -2, то a становится 4294967292
Помните, что -1, присвоенное unsigned int, становится наибольшим unsigned int. Кроме того, поскольку беззнаковая арифметика является модульной, умножение не переполнилось — оно завернулось по кругу.
Пример
unsigned max = 100000; // "случайная опечатка", имелось в виду 10'000
unsigned short x = 100;
while (x < max) x += 100; // бесконечный цикл
Если бы x было знаковым short, мы могли бы предупредить о неопределённом поведении при переполнении.
Альтернативы
- используйте знаковые целые и проверяйте
x >= 0 - используйте тип положительного целого числа
- используйте тип поддиапазона целых чисел
Assert(-1 < x)
Например:
struct Positive {
int val;
Positive(int x) :val{x} { Assert(0 < x); }
operator int() { return val; }
};
int f(Positive arg) { return arg; }
int r1 = f(2);
int r2 = f(-2); // бросает исключение
Примечание
???
Контроль
Смотрите правила контроля ES.100.