Как безопасно читать беззнаковый int из потока?

В следующей программе

#include <iostream>
#include <sstream>

int main()
{
    std::istringstream iss("-89");
    std::cout << static_cast<bool>(iss) << iss.good() << iss.fail() << iss.bad() << iss.eof() << '\n';
    unsigned int u;
    iss >> u;
    std::cout << static_cast<bool>(iss) << iss.good() << iss.fail() << iss.bad() << iss.eof() << '\n';
    return 0;
}

потоки lib читают знаковое значение в unsigned int без даже икоты, молча приводят к неправильному результату:

11000
10001

Нам нужно улавливать ошибки несоответствия типа времени выполнения. Если бы мы не просто поймали это в симуляции, это могло бы взорвать очень дорогое оборудование.

Как мы можем безопасно читать значение unsigned из потока?

Ответы

Ответ 1

Вы можете прочитать переменную типа подписанного типа, которая сначала может обрабатывать весь диапазон и проверить, является ли она отрицательной или превышает максимальный целевой тип. Если ваши значения без знака могут не вписываться в самый большой доступный тип подписки, вам придется выполнять синтаксический анализ с использованием чего-то другого, кроме iostreams.

Ответ 2

Вы можете написать манипулятор:

template <typename T>
struct ExtractUnsigned
{
    T& value;
    ExtractUnsigned(T& value) : value(value) {}
    void read(std::istream& stream) const {
        char c;
        stream >> c;
        if(c == '-') throw std::runtime_error("Invalid unsigned number");
        stream.putback(c);
        stream >> value;
    }
};

template <typename T>
inline ExtractUnsigned<T> extract_unsigned(T& value) {
    return ExtractUnsigned<T>(value);
}

template <typename T>
inline std::istream& operator >> (std::istream& stream, const ExtractUnsigned<T>& extract) {
    extract.read(stream);
    return stream;
}


int main()
{
    std::istringstream data("   +89   -89");
    unsigned u;
    data >> extract_unsigned(u);
    std::cout << u << '\n';
    data >> extract_unsigned(u);
    return 0;
}

Ответ 3

Во-первых, я думаю, что разбор отрицательного значения для значения unsigned неверен. Значение декодируется символом std::num_get<char> в соответствии с форматом strtoull() (22.4.2.12, пункт 3, этап 3, вторая пуля). Формат strtoull() определен в C 7.22.1.4 как тот же, что и для целочисленных констант в C 6.4.4.1, который требует, чтобы буквальное значение представлялось типом unsigned. Очевидно, что отрицательное значение не может быть представлено типом unsigned. По общему признанию, я посмотрел на C11, который я на самом деле не являюсь стандартом C, на который ссылается С++ 11. Кроме того, цитирование стандартных параграфов в компиляторе не решит проблему. Следовательно, ниже приведен подход, который аккуратно изменяет декодирование значений.

Вы можете создать глобальный std::locale с факетом std::num_get<...>, отклоняющим строки, начинающиеся со знака минус для unsigned long и unsigned long long. Обозначение do_put() может просто проверять первый символ и затем делегировать версию базового класса, если это не '-'.

Ниже приведен код для настраиваемого фасета. Хотя это довольно немного кода, фактическое использование довольно прямолинейно. Большая часть кода является только шаблоном, перекрывающим различные функции virtual, используемые для анализа номера unsigned (т.е. Членов do_get()). Все они просто реализованы в терминах шаблона функции-члена get_impl(), который проверяет, нет ли более символов или следующий символ является '-'. В любом из этих двух случаев преобразование завершается неудачей, добавляя std::ios_base::failbit к параметру err. В противном случае функция просто делегирует преобразование базового класса.

Соответственно созданная грань в конечном итоге используется для построения нового объекта std::locale (custom; обратите внимание, что выделенный объект positive_num_get автоматически освобождается, когда освобождается последний std::locale объект, где он используется). Этот std::locale установлен, чтобы стать глобальной локалью. Глобальная локаль используется всеми вновь созданными потоками. Существующие потоки в примере std::cin должны быть imbue() d с локалью, если это должно повлиять на них. Как только глобальная локаль настроена, вновь созданный поток просто подберет измененные правила декодирования, т.е. Не нужно будет сильно менять код.

#include <iostream>
#include <sstream>
#include <locale>

class positive_num_get
    : public std::num_get<char> {
    typedef std::num_get<char>::iter_type iter_type;
    typedef std::num_get<char>::char_type char_type;

    // actual implementation: if there is no character or it is a '-' fail
    template <typename T>
    iter_type get_impl(iter_type in, iter_type end,
                       std::ios_base& str, std::ios_base::iostate& err,
                       T& val) const {
        if (in == end || *in == '-') {
            err |= std::ios_base::failbit;
            return in;
        }
        else {
            return this->std::num_get<char>::do_get(in, end, str, err, val);
        }
    }
    // overrides of the various virtual functions
    iter_type do_get(iter_type in, iter_type end,
                     std::ios_base& str, std::ios_base::iostate& err,
                     unsigned short& val) const override {
        return this->get_impl(in, end, str, err, val);
    }
    iter_type do_get(iter_type in, iter_type end,
                     std::ios_base& str, std::ios_base::iostate& err,
                     unsigned int& val) const override {
        return this->get_impl(in, end, str, err, val);
    }
    iter_type do_get(iter_type in, iter_type end,
                     std::ios_base& str, std::ios_base::iostate& err,
                     unsigned long& val) const override {
        return this->get_impl(in, end, str, err, val);
    }
    iter_type do_get(iter_type in, iter_type end,
                     std::ios_base& str, std::ios_base::iostate& err,
                     unsigned long long& val) const override {
        return this->get_impl(in, end, str, err, val);
    }
};

void read(std::string const& input)
{
    std::istringstream in(input);
    unsigned long value;
    if (in >> value) {
        std::cout << "read " << value << " from '" << input << '\n';
    }
    else {
        std::cout << "failed to read value from '" << input << '\n';
    }
}

int main()
{
    read("\t 17");
    read("\t -18");

    std::locale custom(std::locale(), new positive_num_get);
    std::locale::global(custom);
    std::cin.imbue(custom);

    read("\t 19");
    read("\t -20");
}

Ответ 4

Вы можете сделать это следующим образом:

#include <iostream>
#include <sstream>

int main()
{
        std::istringstream iss("-89");
        std::cout << static_cast<bool>(iss) << iss.good() << iss.fail() << iss.bad() << iss.eof() << '\n';
        int u;
        if ((iss >> u) && (u > 0)) {
                unsigned int u1 = static_cast<unsigned int>(u);
                std::cout << "No errors: " << u1 << std::endl;
        } else {
                std::cout << "Error" << std::endl;
        }
        std::cout << static_cast<bool>(iss) << iss.good() << iss.fail() << iss.bad() << iss.eof() << '\n';
        return 0;
}