我正在创建一个基于小块的游戏。游戏中的项目将其位置存储在存储桶矩阵中。我已将其实现为名为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。
答案 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 == true
和end()
方法总是返回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;
}