Подтвердить что ты не робот

Использование unsigned int vs. проверка, если отрицательный

Скажем, у меня есть класс с целым числом, которое должно быть всегда положительным с простым C'tor:

class A {
    unsigned int x;
    public:
    A(unsigned int X) : x(X) {}
};

И скажем, кто-то случайно создает объект A со значением -5. Конечно, это недопустимо, и X получит действительно огромное значение, так как MSB теперь не обозначает знак числа. Проблема в том, что теперь я не могу проверить, является ли число отрицательным или недействительным (может быть, поразрядным?).

Должен ли я просто избегать unsigned и просто использовать простой int? Поступая таким образом, я мог бы просто выбросить исключение, если заданное значение превышает максимальное значение или ниже нуля.

Хотелось бы услышать некоторые предложения.

4b9b3361

Ответ 1

Приходят в голову два подхода:

  • Добавьте явное преобразование для "подписанных" типов.

    #include <cassert>
    
    class A {
        unsigned int x;
        public:
        A(unsigned int X) : x(X) {}
        explicit A(int X) : x(static_cast<unsigned>(X)) {
            assert(X>=0); // note X, not x!
        }
    };
    
    int main()
    {
        A ok(5);
        A bad(-5);
    }
    
  • Запретить неявные преобразования, удалив лучшие перегрузки:

    A(int X) = delete;
    A(long X) = delete;
    A(char X) = delete;
    

    Это приведет к тому, что все пользователи будут использовать неподписанные объекты перед созданием экземпляра A. Это безопасно, но неуклюже.

Обратите внимание, что это означает, что не запрещает неявные преобразования из всех интегральных типов (например, enum s), поэтому вам нужно сделать больше, чтобы сделать это доказательство дурака.

Вот пример, основанный на SFINAE, который принимает все неявные преобразования кроме, если они содержат подписанные значения: Live on Coliru

#include <type_traits>
#include <limits>

class A {
    unsigned int x;
    public:
    template<typename T, typename = typename std::enable_if<std::is_integral<T>::value, void>::type>
    A(T X) : x(X)
    {
        static_assert(!std::numeric_limits<T>::is_signed, "Signed types cannot be accepted");
    }
};

int main()
{
    A ok(5u);
    A bad(-5);
}

Ответ 2

И скажем, кто-то случайно создает объект A со значением -5

Хотя может быть хорошей практикой сделать вашу программу достаточно прочной, чтобы принимать такие ошибки, основной причиной ошибки является неаккуратный программист, у которого нет достаточных предупреждений.

Чтобы понять суть проблемы, вам необходимо убедиться, что код скомпилирован с включенными предупреждениями и, возможно, также рассмотрит возможность использования внешнего инструмента статического анализа.

Ответ 3

Я, вероятно, согласен с DanielKO о pattern will pop up everywhere. Обратите внимание, что решение на основе SFINAE не работает для uint64 → uint32 усечения. Итак, мой ответ:

class A {
 public:
  using value_type = unsigned int;

  template <class T>
  explicit A(T x): x_(boost::numeric_cast<value_type>(x)) { // can be check in debug only
    static_assert(std::is_integral<T>::value, "T must be integral");
  }

 private:
  value_type x_;
};

И живой пример.