简体   繁体   English

自定义迭代器:如果a和b的行为不同,如何正确处理距离计算和相等比较

[英]Custom iterator: how to correctly handle distance calculation and equality comparison between a and b if the behavior of those differe

I wrote a class PointerIterator which provides a random access iterator for arrays. 我写了一个PointerIterator类,它为数组提供了一个随机访问迭代器。 I know pointers itself are valid iterators but my implementation allows to specify a unit . 我知道指针本身是有效的迭代器,但是我的实现允许指定一个单位 This unit specifies the step that is taken by each increment or decrement. 单位指定每个增量或减量所采取的步骤。 The reason behind is to enable ie iteration over a row in an n x m matrix where the matrix is stored as a single array in row major order. 背后的原因是要在n x m矩阵中的行上进行迭代,其中矩阵以行主顺序存储为单个数组。

However, because unit is unknown at compile time it has to be specified as a ctor argument at runtime. 但是,由于单位在编译时未知,因此必须在运行时将其指定为ctor参数。 This leads to the problem that two iterators of the same type can yield different behaviors (if unit is different from each other). 这导致了一个问题,两个相同类型的迭代器会产生不同的行为(如果单位彼此不同)。

Example

Let m be a 4x4 matrix represented by a single array (in row major order). m为一个由单个数组表示的4x4矩阵(按行主顺序)。

auto rowSize = 4;
auto colSize = 4;
auto m = new int[rowSize * colSize];

// m is somehow filled with increasing numbers, like:

  0   1   2   3    
  4   5   6   7
  8   9  10  11
 12  13  14  15

To iterate over the first row of A , two PointerIterators are created (to mark the beginning and end of the range) 要遍历A的第一行, 创建两个PointerIterators(以标记范围的开始和结束)

auto begin = makePointerIterator(m, 1);
auto end = begin + rowSize;

where makePointerIterator(pointer, unit) creates a PointerIterator for the pointer pointer with step unit . 其中makePointerIterator(pointer, unit)为带有step unit的指针指针创建PointerIterator。

With begin and end from above, we can use a for loop to print the first row, like: 从上面beginend ,我们可以使用for循环来打印第一行,例如:

for (auto it = begin; it != end; ++it)
    cout << std::setw(3) << *it << " ";

which yields 产生

  0   1   2   3

To iterate over the first column, we have to change begin and end to 要遍历第一列,我们必须将beginend更改为

auto begin = makePointerIterator(m, rowSize);
auto end = begin + colSize;

Each increment (ie operator++ ) causes the pointer to step forward by rowSize which points to the next element in the column. 每个增量(即operator++ )都使指针向前移rowSize ,该行指向该列中的下一个元素。

Printing the column can now be done by the same for loop above, which now yields: 现在可以通过上面的for循环来完成列的打印,从而产生:

  0   4   8  12

The problem 问题

In order to make a valid random access iterator, I have to provide (amongst others) 为了制作有效的随机访问迭代器,我必须提供(以及其他方法)

difference_type operator-(PointerIterator other) const;
bool operator==(PointerIterator other) const;
bool operator!=(PointerIterator other) const;

Lets say I have two PointerIterators a and b where a.unit == 1 and b.unit == 2 which means a increases the pointer by 1 and b increases (or decreases) the pointer by 2 at each increment (or decrement). 假设我有两个PointerIterators ab ,其中a.unit == 1b.unit == 2 ,这意味着a在每次增加(或减少)时将指针增加1,而b将指针增加(或减少)2。

How do I have to implement the 3 functions above while keeping the iterator standard conform? 在保持迭代器标准一致的同时,如何实现上述3个功能?

The possible solutions that came in my mind were: 我想到的可能的解决方案是:

  1. Preemptively check if a.unit == b.unit and if not, throw an exception. 抢先检查a.unit == b.unit ,如果不是,则引发异常。
  2. Use always a.unit (which is at the left hand side of the operators), which would cause that a == b may not be equal to b == a . 始终使用a.unit (位于运算符的左侧),这将导致a == b可能不等于b == a

Edit 编辑

I put the implementation on codereview in case someone is interested in it. 我将实现放在codereview上 ,以防有人对此感兴趣。

Iterators of the same type but with different units can be treated the same way as iterators of the same type but pointing into different containers. 相同类型但具有不同单元的迭代器可以与相同类型但指向不同容器的迭代器以相同方式处理。 That is, it's the programmer's responsibility to make sure the units are the same, otherwise it's Undefined Behaviour to mix (compare or subtract) the iterators. 也就是说,确保单位相同是程序员的责任,否则混合(比较或减去)迭代器是未定义行为。

First, undefined behavior is your friend. 首先,未定义的行为是您的朋友。 Have "safe" operations that throw or use algebraic error types to indicate a mismatch, but the == operation can simply make the result undefined, like comparing iterators of different containers are. 具有抛出或使用代数错误类型表示不匹配的“安全”操作,但是==操作可以使结果变得不确定,就像比较不同容器的迭代器一样。

Second, consider doing this: 其次,考虑这样做:

template<class Unit, class=void>
struct default_unit_value {};

template<class Unit,
  std::enable_if_t< std::is_integral<Unit>{} >
>
struct default_unit_value:std::integral_constant<Unit, 1> {};

template<class T, class Unit=std::ptrdiff_t>
struct PointerIterator {
  PointerIterator(T* p = nullptr, Unit u = default_unit_value<Unit>::value):
    ptr(p), unit(u)
  {}
private:
  T* ptr;
  Unit unit; // actually use a compressed pair, in case Unit is stateless
};

this permits compile-time constant unit sizes to be used and mismatch errors in those cases to be checked at compile time. 这允许使用编译时常量单位大小,并在编译时检查那些情况下的不匹配错误。

auto begin = makePointerIterator<1>(m);
auto end = begin + rowSize;

constexpr rowSize = 4;
auto begin = makePointerIterator<rowSize>(m);
auto end = begin + colSize;

meanwhile, runtime variable stride is still available. 同时,运行时变量跨度仍然可用。

In a large number of cases you do know the element stride distance at compile time. 在很多情况下,您确实知道编译时的元素步距。 Telling the compiler explicitly will generate better code. 明确告诉编译器将生成更好的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM