输入/输出迭代器

时间:2016-07-17 22:09:54

标签: c++ iterator equality equivalence

我正在创建一个基于小块的游戏。游戏中的项目将其位置存储在存储桶矩阵中。我已将其实现为名为Grid的类模板,其中包含名为Tile的存储桶类。

Grid基本上只是std::vector的包装器,带有各种存取方法,用于将coords转换为索引键。它还转发了向量的迭代器,以便我可以遍历Tiles中的所有Grid

有时虽然我只需要遍历Grid的子部分。所以我实现了一个名为Section的小类,它在构造函数中采用两组coords来定义AABB。 begin()的{​​{1}}和end()方法返回输入/输出迭代器,用于循环遍历AABB内的所有切片。

它的全部工作,但我试图保持迭代器的性能尽可能接近嵌套循环。基本上使用基于Section的范围不应该比:

贵得多
Section

这让我想到了问题。我希望不等式运算符尽可能简单,所以我已经实现了它:

for (size_t y = 0, end_y = NUM; y < end_y; ++y)
{
    for (size_t x = 0, end_x = NUM; x < end_x; ++x)
    {
        auto& tile = grid[coords_to_key(x, y)];
    }
}

由于迭代器按顺序扫描bool operator!=(const Section_Iterator& other) const { return m_coords.y < other.m_coords.y; } 中的每一行,我们知道在Section时我们已经“结束”了。这意味着我的不等式运算符适用于基于范围的for循环,因为在引擎盖下它们只检查iterator.y >= end.y

虽然运营商的实施看起来很奇怪。喜欢 非常奇怪。 例如iterator != end可能是真或假。这取决于预增量是否导致迭代器跳转到下一行。

我一直在看标准,我认为我很清楚,因为他们区分了平等和等同。

来自http://en.cppreference.com/w/cpp/concept/InputIterator

  

注意,“在==域中”表示在两个迭代器值之间定义了相等比较。对于输入迭代器,不需要为所有值定义相等比较,并且==域中的值集可能随时间而变化。

来自http://en.cppreference.com/w/cpp/concept/OutputIterator

  

可能无法为输出迭代器定义平等和不等式。即使定义了运算符==,x == y也不需要暗示++ x == ++ y。

但是,老实说,标准让我头晕目眩。我的合法性是什么?

2 个答案:

答案 0 :(得分:1)

经过更多的研究后发现,根据标准,我所做的事情并不合法。

输入迭代器必须为EqualityComparable。这意味着:

  
      
  • 对于a的所有值,a == a得到真。
  •   
  • 如果a == b,则b == a
  •   
  • 如果a == b且b == c,则a == c
  •   

使用我当前的等于运算符a == b并不意味着b == a

为了解决我的问题,我查看了std::istream_iterator,它是一个输入迭代器的实现,当然它所做的任何事情都必须符合标准。它的相等运算符的行为如下所示:

  

检查lhs和rhs是否相等。如果它们都是流末尾迭代器或两者都引用相同的流,则两个流迭代器是相等的

基本上,如果两个迭代器都有效,则它们相等。如果他们都“结束”他们比较平等。如果一个是有效的,但一个是“过了结束”,那么它们是不相等的。

将相同的逻辑应用于我的Section::iterator很容易。迭代器现在包含一个bool m_valid。方法begin()总是返回一个迭代器,其中m_valid == trueend()方法总是返回m_valid == false的迭代器。

迭代器的预增量运算符现在测试它是否超过结束并相应地设置bool。

Section_Iterator& operator++()
{
    ++m_coords.x;
    if (m_coords.x >= m_section.m_max.x)
    {
        m_coords.x = m_section.m_min.x;
        ++m_coords.y;
        m_valid = (m_coords.y < m_section.m_max.y);
    }

    return *this;
}

现在,相等运算符非常易于理解并具有一致的行为。指向Tile中的Section的任何迭代器都是有效的,并且与任何其他有效迭代器的比较等于。

bool operator==(const Section_Iterator& other) const
{
    return m_valid == other.m_valid;
}

bool operator!=(const Section_Iterator& other) const
{
    return m_valid != other.m_valid;
}

答案 1 :(得分:0)

老实说,我不知道你在上面所做的事情是否合法。然而,它确实具有奇怪的语义,即使它是合法的。

相反,我会考虑这样的事情来解决你的问题:

#include <iostream>
#include <vector>

struct Grid
{
    std::vector<int> tiles;
    size_t rows;
    size_t cols;
};

class SectionIterator
{
public:
    SectionIterator(Grid * grid, size_t row, size_t col, size_t endRow) :
            m_row{ row },
            m_col{ col },
            m_startRow{ row },
            m_endRow{ endRow },
            m_grid{ grid }
    {
    }

    SectionIterator & operator++()
    {
        ++m_row;
        if (m_row == m_endRow)
        {
            m_row = m_startRow;
            ++m_col;
        }
        return *this;
    }

    bool operator==(const SectionIterator & other) 
    {
        return (m_grid == other.m_grid) 
                && (m_row == other.m_row)
                && (m_col == other.m_col);
    }

    bool operator!=(const SectionIterator & other) 
    {
        return !(*this == other);
    }

    int & operator*() 
    {
        return m_grid->tiles[m_col * m_grid->rows + m_row];
    }

    int * operator->() 
    {
        return &operator*();
    }
private:
    size_t m_row;
    size_t m_col;
    size_t m_startRow;
    size_t m_endRow;
    Grid * m_grid;

};

struct Section 
{
    SectionIterator m_begin;
    SectionIterator m_end;

    SectionIterator begin() { return m_begin; }
    SectionIterator end() { return m_end; }
};

int main() 
{
    Grid grid{ std::vector<int>{ 1, 2, 3, 4, 5, 6 }, 2, 3 };
    // 1, 3, 5 
    // 2, 4, 6

    // look up start and end row and col
    // end positions are found by looking up row/col of section end and then adding one
    size_t startRow = 0;
    size_t endRow = 2;
    size_t startCol = 1;
    size_t endCol = 3;

    SectionIterator begin = SectionIterator{ &grid, startRow, startCol, endRow };
    // Note that the end iterator actually has startRow as its startRow, not endRow, because operator++ will set the iterator's m_row back to startRow so this will make it equal the end iterator once the iteration is complete
    SectionIterator end = SectionIterator{ &grid, startRow, endCol, endRow };
    for (int v : Section{ begin, end })
    {
        std::cout << v << std::endl;
    }
    return 0;
}

请注意,这预示着您有一些功能可以在网格中的坐标和行/列索引之间进行转换。此外,上面按列主要顺序进行迭代,但可以很容易地更改为按行主顺序迭代。

修改

为了阐明从浮点坐标到索引的转换是如何工作的,请考虑以下内容。

我假设您的图块被定义为每个图块覆盖1x1平方的浮点坐标。例如,tile(0,0)覆盖浮点间隔[0.0,1.0),[0.0,1.0),tile(2,2)覆盖间隔[2.0,3.0),[2.0,3.0]。我相信这就是你描述当前设置的方式。

如果你想迭代从(1.2,1.2)到(4.2,4.2)的部分内的所有图块,首先通过截断将这些点转换为row,col index:

(1.2,1.2)= tile(1,1) (4.2,4.2)= tile(4,4)

这意味着您希望在闭合闭合间隔[1,4]和闭合闭合间隔[1,4]中的列中迭代行。由于像上面那样的迭代器使用闭合打开的间隔,因此必须在结束索引中加1,这样传递给迭代器的值表示行的间隔[1,5]和列的[1,5]。请注意,这些间隔实际上与闭合闭合间隔形式相同,但结束值表示&#34;一个超过您想要取消引用的最后一个索引&#34;。

编辑#2

您表示您确实希望确保您的部分以浮点坐标的开放时间间隔结束,因此(1.0,1.0)到(4.0,4.0)包含3个tile行和3个tile列,而不是4。

您可以通过将结束索引与原始值进行比较来实现此目的,如果它不是整数则只添加1,所以

float coord = ...;
size_t idx = static_cast<size_t>(coord);
constexpr float tolerance = 1e-6f;
if (std::abs(coord - idx) > tolerance)
{
    // Add 1 to make the range include the last tile
    ++idx;
}