如何安全地从流中读取无符号的 int?[复制]

How to safely read an unsigned int from a stream? [duplicate]

提问人:sbi 提问时间:1/13/2014 最后编辑:Bartek Banachewiczsbi 更新时间:1/14/2014 访问量:3010

问:

在以下程序中

#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;
}

streams lib 将一个有符号值读入一个甚至没有打嗝,静默地产生一个错误的结果unsigned int

11000
10001

我们需要能够捕获这些运行时类型不匹配错误。如果我们没有在模拟中发现这一点,这可能会炸毁非常昂贵的硬件。

我们如何安全地从流中读取无符号值?

C++ iostream

评论

1赞 Stefano Falasca 1/13/2014
看看 boost::numerical_cast boost.org/doc/libs/1_55_0/libs/numeric/conversion/doc/html/...
2赞 chris 1/13/2014
哇,TIL 现在只是早上。
2赞 Nawaz 1/13/2014
此代码的预期输出应该是什么?

答:

9赞 R. Martinho Fernandes 1/13/2014 #1

您可以读入一个有符号类型的变量,该变量可以首先处理整个范围,并测试它是负数还是超出目标类型的最大值。如果无符号值可能不适合可用的最大有符号类型,则必须使用 iostreams 以外的其他方法进行分析。

-1赞 vershov 1/13/2014 #2

您可以按如下方式操作:

#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;
}

评论

0赞 Konrad Rudolph 1/13/2014
这并不适用于所有值。特别是,的值将产生错误的结果(实际上,任何设置了最高位的内容)。static_cast<unsigned>(-1)
0赞 Bartek Banachewicz 1/13/2014
@KonradRudolph呢?u > 0
0赞 Konrad Rudolph 1/13/2014
@Bartek关键是这不是错误。
5赞 Dietmar Kühl 1/13/2014 #3

首先,我认为为值解析负值是错误的。该值按照(22.4.2.12 第 3 段,第 3 阶段,第二个项目符号)的格式解码。C 7.22.1.4 中定义的格式与 C 6.4.4.1 中的整数常量的格式相同,这要求文本值可以由类型表示。显然,负值不能由类型表示。诚然,我看了 C11,它并不是真正从 C++11 引用的 C 标准。此外,在编译器中引用标准段落并不能解决问题。因此,下面是一种巧妙地改变值解码的方法。unsignedstd::num_get<char>strtoull()strtoull()unsignedunsigned

您可以设置一个全局变量,其中包含一个分面拒绝以减号 和 开头的字符串。覆盖可以简单地检查第一个字符,然后委托给基类版本(如果它不是 .std::localestd::num_get<...>unsigned longunsigned long longdo_put()'-'

下面是自定义分面的代码。虽然是相当多的代码,但实际使用相当直接。大多数代码只是样板代码,覆盖了用于解析数字的不同函数(即成员)。这些都只是根据成员函数模板实现的,该模板检查是否没有更多字符,或者下一个字符是否是 .在这两种情况下,通过向参数添加 进行转换失败。否则,该函数仅委托给基类转换。virtualunsigneddo_get()get_impl()'-'std::ios_base::failbiterr

相应创建的 facet 最终用于构造一个新对象(请注意,当使用它的最后一个对象被释放时,分配的对象会自动释放)。安装后,此区域设置将成为全局区域设置。全局区域设置由所有新创建的流使用。在示例中,现有流需要与区域设置一起 d,如果它会影响它们。设置全局语言环境后,新创建的流将只拾取更改的解码规则,即不需要更改代码。std::localecustompositive_num_getstd::localestd::localestd::cinimbue()

#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");
}
11赞 user2249683 1/13/2014 #4

你可以写一个操纵器:

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;
}

评论

0赞 stijn 1/13/2014
如果前面有空格,这也有效吗?-
1赞 1/13/2014
@stijn 是的,它是格式化的输入