如何安全地从流中读取无符号整数?

17

在下面的程序中

#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库在不出错的情况下将一个有符号值读入无符号整数中,默默地产生了错误结果

11000
10001

我们需要能够捕获那些运行时类型不匹配的错误。如果我们没有在模拟中捕获到这个错误,这可能会导致非常昂贵的硬件损坏。

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


1
请查看boost::numerical_cast http://www.boost.org/doc/libs/1_55_0/libs/numeric/conversion/doc/html/boost_numericconversion/improved_numeric_cast__.html - Stefano Falasca
2
哇,今天早上就学到了新知识。 - chris
2
这段代码的预期输出是什么? - Nawaz
4个回答

11

你可以编写一个操作器:

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

如果在“-”之前有空格,这个方法还有效吗? - stijn
1
@stijn 是的,它是格式化输入。 - user2249683

9

您可以先将值读入一个有能力处理整个范围的有符号类型的变量中,然后测试它是否为负或超出目标类型的最大值。如果您的无符号值可能不适合于可用的最大有符号类型,则必须使用除iostream之外的其他内容进行解析。


5
首先,我认为对于无符号值解析负值是错误的。该值由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引用的C标准。此外,在编译器引用标准段落也无法解决问题。因此,下面是一种简洁地更改值解码的方法。
您可以使用拒绝以负号开头的字符串的std::num_get<...>facet为unsigned longunsigned long long设置全局std::localedo_put()覆盖可以简单地检查第一个字符,如果不是'-'则委托给基类版本。
以下是自定义facet的代码。虽然这是相当多的代码,但实际使用非常简单。大部分代码只是模板覆盖不同virtual函数,用于解析unsigned数字(即,do_get()成员)。这些都是基于成员函数模板get_impl()实现的,该模板检查是否没有更多字符或下一个字符是否为'-'。在这两种情况下,通过将std::ios_base::failbit添加到参数err中来使转换失败。否则,该函数仅委托给基类转换。
最终创建的facet最终用于构建新的std::locale对象(custom;请注意,分配的positive_num_get对象在使用它的最后一个std::locale对象被释放时会自动释放)。此std::locale被安装为全局语言环境。全局语言环境由所有新创建的流使用。对于现有流,在示例中为std::cin,如果应该影响它们,则需要imbue()使用语言环境。一旦设置了全局区域设置,新创建的流将只需选择已更改的解码规则,因此不应太需要更改代码。
#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");
}

-1
你可以按照以下方式实现:
#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;
}

这并不适用于所有值。特别是,static_cast<unsigned>(-1) 的值会产生错误的结果(实际上是任何最高位设置为1的值)。 - Konrad Rudolph
@KonradRudolph u > 0 怎么样? - Bartek Banachewicz
@Bartek 重点在于它不是一个错误。 - Konrad Rudolph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接