Как безопасно читать беззнаковый 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;
}