我有一个带有6个方向条目(N,NE,SE,S,SW,NW)的方向枚举,它们基本上是图形中节点的边缘。我经常需要迭代所有邻居,这些邻居目前通过仅使用int从0-> 5进行迭代来完成。
有时人们还需要从例如当前通过从2-> 2 + 5迭代并且在使用时将其取为mod 6来完成2-> 1缠绕。
我能做些什么使得更安全/更容易使用而不会失去性能?具有固定整数范围的for循环可以展开,内联等。基于矢量的方法可以(在枚举中使用静态const向量)
我已经在以下方案中使用了枚举:
struct Direction{
enum Type{
N, NE, ...
}
unsigned COUNT = ...;
Type t_;
operator Type(){return t_;}
Direction(Type t):t_(t){assert(N<=t<COUNT);}
explicit Direction(unsigned t):t_(t%COUNT){}
static Direction fromUInt(unsigned t){return Direction(Type(t));}
}
所以我想要的是允许迭代器允许在整个集合上有效迭代,并允许这个任意起始点,在这种情况下迭代器会回绕。
怎么能写这个?我无法解决任何问题。 start = end表示整个循环无效。或者我应该只有start = givenStartType,end = start + COUNT并在每个迭代器deref上做一个模数?
不幸的是,不允许使用C ++ 11
答案 0 :(得分:2)
编辑以回应澄清
你想在每个取消引用上使用迭代器模COUNT
是一个很好的想法。请参阅下面的反向迭代器/可迭代组合。在使用-O3
进行编译后,我检查了程序集输出。编译器展开循环。输出为2 1 0 5 4 3
。您可以实现前向迭代器,或将方向作为参数。您还可以将其转换为枚举类型的模板。
不幸的是,从使用语法的角度来看,我不认为这会在do
- while
循环中为你买得那么多,如另一个答案所示 - 至少在之前没有C ++ 11。它确实隐藏了各种索引变量,并帮助您避免错误,但它更加冗长。
#include <iostream>
struct Direction {
enum Type {N, NE, SE, S, SW, NW};
static const unsigned COUNT = 6;
Type t_;
operator Type() { return t_; }
Direction(Type t) : t_(t) { }
explicit Direction(unsigned t) : t_(Type(t % COUNT)) { }
};
struct ReverseIterable {
const unsigned start_;
struct iterator {
unsigned offset_;
explicit iterator(unsigned offset) : offset_(offset) { }
Direction operator *() { return Direction(offset_); }
iterator& operator++() { --offset_; return *this; }
bool operator ==(const iterator &other)
{ return offset_ == other.offset_; }
bool operator !=(const iterator &other) { return !(*this == other); }
};
explicit ReverseIterable(Direction start) : start_(start) { }
iterator begin() { return iterator(start_ + Direction::COUNT); }
iterator end() { return iterator(start_); }
};
int main()
{
ReverseIterable range = ReverseIterable(Direction::SE);
for (ReverseIterable::iterator iterator = range.begin();
iterator != range.end(); ++iterator) {
std::cout << (int)*iterator << " ";
}
std::cout << std::endl;
return 0;
}
在C ++ 11中,循环可以是:
for (Direction direction : ReverseIterable(Direction::SE))
std::cout << (int)direction << " ";
std::cout << std::endl;
你可能(ab?)使用宏来获得类似的东西在C ++ 98中。
我暂时保留下面的原始答案,因为如果枚举定义可以更改,并且因为它允许稀疏范围,它可以简化可维护性。可以在它上面实现一个非常相似的迭代器。
原创答案侧重于安全
这可能是完全矫枉过正的目的,而且可能不适合我将进一步描述的原因。但是,您可以使用此库(免责声明:我是作者):https://github.com/aantron/better-enums编写如下代码:
#include <iostream>
#include <enum.h>
ENUM(Direction, int, N, NE, SE, S, SW, NW)
int main()
{
size_t iterations = Direction::_size();
size_t index = 2;
for (size_t count = 0; count < iterations; ++count) {
std::cout << Direction::_values()[index] << " ";
index = (index + 1) % Direction::_size();
}
std::cout << std::endl;
return 0;
}
输出:
SE S SW NW N NE
(值为int
- 大小的枚举,但转换为字符串仅输出到std::cout
。
这显示了整个集合的迭代,具有任意的起始点。你可以让它前进或后退,并在任何枚举上进行模板化。
我认为在你的问题中使用模数是一个好主意。这段代码只是给你一些关于枚举中常量数量的反射信息,并将它们放在一个数组中。
这可能不合适的原因是,由于您没有使用C ++ 11,因此阵列Direction::_values()
将在程序启动时初始化。我认为循环展开仍然可能发生,但编译器将无法对数组的内容做任何事情。该数组仍将静态分配,但在编译期间元素将不可用。
如果您以后可以选择使用C ++ 11,那么该数组将与静态初始化的int[6]
基本相同(实际上,Direction[6]
,其中Direction
是文字struct
类型)。
(实际上,我想我可以公开int
s而不是Direction
s的数组,即使在C ++ 98中也会静态初始化。
答案 1 :(得分:0)
如果您想避免使用自定义库,我通常使用的方法就是这样:
enum Direction
{
SE,
S,
SW,
NW,
N,
NE,
DIRECTION_FIRST = SE,
DIRECTION_LAST = NE,
}
然后,您可以使用DIRECTION_FIRST
和DIRECTION_LAST
来正确迭代范围。如果有人在不更新迭代端点的情况下向枚举添加值,则仍然存在错误的空间,但是将其集中在枚举中应该会降低这种可能性。
现在,如果您假设一个任意的起始方向start
,您可以像这样迭代:
Direction current = start;
do
{
// Do stuff with current...
current = (current + 1) % DIRECTION_LAST;
} while(current != start);
(如果您的枚举不是从0开始,那么逻辑会有点复杂,但仍有可能。这可能是您唯一需要使用{{1}的时间}。)