不使用std::vector或std::list内部的C++ std::iterator

3

作为一项练习,我尝试在C++中创建一个简单的数字范围类。它将允许您遍历均匀分布的双精度浮点数(类似于numpy/Python的arange):

我想要做的(但使用迭代器):

double lower = ..., upper = ..., delta = ...;
for (double val = lower; val < upper; val += delta)
{
   // do something with val
   f(val);
}
// include the last val to guarantee upper is included or exceeded
f(val); // do something with val

期望的等效迭代器代码:

double lower = ..., upper = ..., delta = ...;
NumericRange nr(lower, upper, delta);
for (NumericRange::const_iterator iter = nr.begin(); iter != nr.end(); iter++)
{
    f(*iter);
}

我希望我的迭代器与STL迭代器兼容,以便可以重用代码(通过NumericRange迭代应该等同于通过std::vector迭代)。
我成功地将值存储在std::vector中(然后使用std::vector的迭代器)。这是我在线上找到的所有解决方案。然而,完全没有必要存储整个列表。
有没有一种方法可以避免存储完整的值集?有没有一些可迭代的类可以继承并覆盖++、==等来实现所需的效果,而不存储std::vector?
(我真的很想知道如何在没有BOOST的情况下做到这一点,即使BOOST很好。我之所以这样问是因为我想学习如何从头开始编写像BOOST这样的东西。我肯定知道软件工程的一部分是使用别人创建的工具,但我真的想学习这些工具的设计和构建方式。)
我的可迭代NumericRange类(内部使用std::vector):
class NumericRange
{
protected:
  double lower, upper, delta;
  std::vector<double> sorted_range;
public:
  typedef std::vector<double>::const_iterator const_iterator;
  NumericRange()
  {
    lower = upper = delta = std::numeric_limits<double>::quiet_NaN();
    // vector is constructed empty
  }
  NumericRange(double lower_param, double upper_param, double delta_param)
  {
    lower = lower_param;

    upper = upper_param;
    delta = delta_param;
    assert(upper_param > lower_param);

    double val;
    // note: can be much faster without push_back
    for (val = lower_param; val < upper_param; val += delta_param)
      {
    sorted_range.push_back(val);
      }
    // ensure the upper_value is contained or surpassed
    sorted_range.push_back(val);
  }
  // to prevent comparison of the entire vector
  bool operator ==(const NumericRange & rhs) const
  {
    return lower == rhs.lower && upper == rhs.upper && delta == rhs.delta;
  }
  // note: this class doesn't really need to store the values in a
  // vector, but it makes the iterator interface much easier.
  const_iterator begin() const
  {
    return sorted_range.begin();
  }
  const_iterator end() const
  {
    return sorted_range.end();
  }
  double get_lower() const
  {
    return lower;
  }
  double get_upper() const
  {
    return upper;
  }
  double get_delta() const
  {
    return delta;
  }
  size_t size() const
  {
    return sorted_range.size();
  }
  void print() const
  {
    std::cout << "[ " << lower << " : " << upper << ": +=" << delta << " ]" << std::endl;
  }
};

为什么因宗教原因而不使用增强功能? - Mr Lister
1
@MrLister 这主要是作为一个玩笑,但我已经在上面添加了澄清。 - user
好的。嘿,我并不反对想要自己做一些事情而不是使用库。甚至可以重新发明轮子,只要像你说的那样作为“练习”。但是后来你开始说“宗教原因”,所以我想你可能对 Boost 有一些根本性问题。 - Mr Lister
你能否请把那个加粗的 PPS 删掉吗?这样对我来说会分散注意力,而不利于理解这个非常有价值的问题和答案。 - thomastiger
@thomastiger 说得好。有很多“为什么不使用BOOST答案”的回答,然后是一场我试图平息的评论战。但现在这些评论已被删除,我完全同意你的观点并进行了编辑。 - user
1个回答

5
有没有一些可迭代的类可以继承并覆盖 ++, == 等函数以达到所需效果,而不需要存储 std::vector<double>
是的,有这样一个类,它的名字叫做 std::iterator<std::input_iterator_tag, double>
以下是使用 int 的起始代码。为了节省大脑空间,我使用同一个类来表示范围和迭代器。
#include <iterator>
#include <iostream>

struct NumericRange : public std::iterator< std::input_iterator_tag, int >
{
  int current, fini, delta;
  typedef NumericRange iterator;
  typedef iterator const_iterator;
  iterator begin() { return *this; }
  iterator end() { return iterator(fini, fini, delta); }
  iterator& operator++() { current += delta; return *this; }
  iterator operator++(int) { iterator result(*this); ++*this; return result; }
  int operator*() const { return current; }
  NumericRange(int start, int fini, int delta) 
    : current(start), fini(fini), delta(delta)
  {
  }
  bool operator==(const iterator& rhs) {
    return rhs.current == current;
  }
  bool operator!=(const iterator& rhs) {
    return !(*this == rhs);
  }
};

void f(int i, int j) {
  std::cout << i << " " << j << "\n";
}

int main () {
  int lower = 4, upper = 14, delta = 5;
  NumericRange nr(lower, upper, delta);
  for (NumericRange::const_iterator iter = nr.begin(); iter != nr.end(); iter++)
  {
      f(*iter, *nr.end());
  }
}

2
真棒,非常感谢。对于 double 类型来说会有点困难,因为 != 操作符会直接比较两个 double 数的相等性,这在数值上是很危险的。为了解决这个问题,你必须保证连续执行 current += delta 最终会使得 current == fini;而这与 fini = current + k*(delta) (其中 k 是整数)不一定相同,由于数值误差的存在。因此,在进行 != 操作时,我建议限制数值误差并在误差范围内测试近似相等性。 - user
是的,这就是为什么我从'int'开始。制作double版本留给大家自己完成。 - Robᵩ

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