c++ - 创建一个自定义整数,强制始终在指定范围内;如何克服整数溢出?

标签 c++ integer-overflow

如题。作为练习,我想创建一个 int 来对其值施加约束,并且不允许将其设置为指定范围之外的值。

这是我尝试解决这个问题的方法:

#include <cassert>
#include <cstdint>
#include <iostream>
using namespace std;

int main();

template<typename valtype, valtype minval, valtype maxval>
class ConstrainedValue
{
  valtype val;

  static bool checkval (valtype val)
  {
    return minval <= val && val <= maxval;
  }

public:
  ConstrainedValue() : val{minval} // so that we're able to write ConstrainedValue i;
  {
    assert(checkval(val));
  }

  ConstrainedValue(valtype val) : val{val}
  {
    assert(checkval(val));
  }

  ConstrainedValue &operator = (valtype val)
  {
    assert(checkval(val));
    this->val = val;
    return *this;
  }

  operator const valtype&() // Not needed here but can be; safe since it returns a const reference
  {
    return val;
  }

  friend ostream &operator << (ostream& out, const ConstrainedValue& v) // Needed because otherwise if valtype is char the output could be bad
  {
    out << +v.val;
    return out;
  }

  friend istream &operator >> (istream& in, ConstrainedValue& v) // this is horrible ugly; I'd love to know how to do it better
  {
    valtype hlp;
    auto hlp2 = +hlp;
    in >> hlp2;
    assert(checkval(hlp2));
    v.val = hlp2;
    return in;
  }
};

int main()
{
  typedef ConstrainedValue<uint_least8_t, 0, 100> ConstrainedInt;
  ConstrainedInt i;
  cin >> i;
  cout << i;
  return 0;
}

问题是……这是行不通的。如果这个自定义整数被赋予溢出其基础类型的值,它只会设置错误的值。

例如,假设我们有 [0; 100],底层类型是 uint_least8_t,如上例所示。 uint_least8_t 评估为 char 或 unsigned char,我不确定是哪个。让我们尝试为这个程序提供不同的值:

10
10

不错。有效。

101
test: test.cpp:52: std::istream& operator>>(std::istream&, ConstrainedValue<unsigned int, 0u, 100u>&): Assertion `checkval(hlp2)' failed.
Aborted

哈哈!正是我想要的。

但是:

257
1

是的。溢出、截断、错误值、未能正确检查范围。

如何解决这个问题?

最佳答案

我认为您遇到了规范问题,不幸的是实现并没有自动解决。

只要你写:ConstrainedValue(valtype val) : val{val}<​​ 你就失去了检测溢出的希望,因为转换为 valtype 发生了在调用代码之前。因为如果 uint_least8_t 被翻译成 unsigned char 这似乎发生在你(和我)的实现中,(uint_least8_t) 257 2

为了能够检测到溢出,您必须在构造函数和 operator = 方法中使用更大的整数类型。

恕我直言,您应该使用模板化构造函数、operator = 和 checkval :

template<typename valtype, valtype minval, valtype maxval>
class ConstrainedValue
{
  valtype val;

    template<typename T> static bool lt(valtype v, T other) {
        if (v <= 0) {
            if (other >= 0) return true;
            else return static_cast<long>(v) <= static_cast<long>(other);
        }
        else {
            if (other <= 0) return false;
            else  return static_cast<unsigned long>(v)
                <= static_cast<unsigned long>(other);
        }
    }


    template <typename T> static bool checkval (T val)
    {
        return lt(minval, val) && (! lt(maxval, val));
    }

public:
  ConstrainedValue() : val{minval} // so that we're able to write ConstrainedValue i;
  {
    assert(checkval(val));
  }

  template<typename T> ConstrainedValue(T val) : val{val}
  {
    assert(checkval(val));
  }

  template<typename T> ConstrainedValue &operator = (T val)
  {
    assert(checkval(val));
    this->val = val;
    return *this;
  }

  operator const valtype&() // Not needed here but can be; safe since it returns a const reference
  {
    return val;
  }

这样,编译器会自动选择合适的类型来避免早期溢出:你在 checkval 中使用原始类型,并使用最好的 long longunsigned long long 对于比较,注意有符号/无符号比较(无编译警告)!

事实上,如果您接受可能的(无害的)有符号/无符号不匹配警告,lt 可以写得更简单:

    template<typename T> static bool lt(valtype v, T other) {
        if (v <= 0) && (other >= 0) return true;
        else if (v >= 0) && (other <= 0) return false;
        else  return v <= other;
        }
    }

如果 valtype 和 T 之一已签名而另一个未签名,则可能会出现警告。它是无害的,因为 v 和 other 符号相反的情况被明确处理,如果两者都是负数,则必须对其进行签名。因此,只有当一个签名而另一个未签名但两者均为正数时,它才会发生。在这种情况下,第 5 条(C++ 编程语言标准的 5 个表达式,第 10 节)保证将使用最大的类型,具有无符号优先级,这意味着它对于正值是正确的。并且它避免强制转换为 unsigned long long

但还有一个情况我处理不当:注入(inject)器。在解码之前,您无法确定输入值是否应该转换为 long longunsigned long long(假设它们是最大可能的整数类型) .我能想到的最简洁的方法是获取字符串形式的值并手动对其进行解码。由于有很多极端情况,我建议您:

  • 首先将其作为字符串获取
  • 如果第一个字符是负号 - 将其解码为 long long
  • 否则解码为unsigned long long

对于非常大的数字,它仍然会给出奇怪的结果,但这是我能做到的最好的结果:

friend std::istream &operator >> (std::istream& in, ConstrainedValue& v)
{
    std::string hlp;
    in >> hlp;
    std::stringstream str(hlp);
    if (hlp[0] == '-') {
        long long hlp2;
        str >> hlp2;
        assert(checkval(hlp2));
        v.val = static_cast<valtype>(hlp2);
    }
    else {
        unsigned long long hlp2;
        str >> hlp2;
        assert(checkval(hlp2));
        v.val = static_cast<valtype>(hlp2);
    }
    return in;
}

关于c++ - 创建一个自定义整数,强制始终在指定范围内;如何克服整数溢出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31452583/

相关文章:

c# - 在 WCF 服务中托管 FireBirdSql 时出现 AccessViolationException

c - 如何判断char数组表示的数字是否会溢出int?

c++ - C++ 标准库是如何在幕后工作的?

c++ - 用户在输出文本时输入控制台

c++ - 如何使用 EvtQuery 函数查找日志文件的大小?

c - 将所有文件内容读入字符串 - 将 off_t 的值传递给 malloc() (其中需要 size_t)是否会遇到问题?

c++ - 在实现 OpenFileDialog 框 ,"System.IO.FileStream"出现在结果编辑文本框而不是文件名和路径上。如何?

c++ - 在 VC++ 项目中开始使用 PCH

c# - 为什么要发生整数溢出?

c# - 为什么 (int)(1.0/x) where x = 0 导致 In32.MinValue 而不是 Int32.MaxValue?