C++迭代器需要是可平凡复制的吗?

huangapple go评论75阅读模式
英文:

Do C++ iterators need to be trivially copyable?

问题

我在考虑创建一个用于解析二进制格式的迭代器。后来我意识到,为了正确解析它,这个迭代器需要不时地堆叠一些上下文。由于 std::stack 不是平凡复制的,我现在有以下问题:

是否拥有非平凡复制的迭代器只是一个坏主意,还是迭代器作为一个概念的要求?

英文:

I was thinking about creating an iterator to parse a binary format. Then I realized that in order to parse it properly, the iterator would need, from time to time, to stack up some context. Since a std::stack is not trivially copyable, I now have the following question:

Is it just a bad idea to have non trivially copyable iterators, or is it a requirement of Iterator as a concept?

答案1

得分: 3

不,迭代器 不必是可平凡复制的:

如果类型 X 满足 Cpp17Iterator 要求,那么:

  • X 满足 Cpp17CopyConstructibleCpp17CopyAssignableCpp17SwappableCpp17Destructible 要求 (utility.arg.requirements, swappable.requirements),以及
  • iterator_traits<X>::difference_type 是有符号整数类型或 void,并且
  • 表 86 中的表达式有效且具有指定的语义。

- [iterator.iterators] §2

C++20 迭代器概念也不强制要求这样的要求。

迭代器不是可平凡复制的,但应尽量轻量化

然而,迭代器通常应尽量轻量化,算法会假定复制迭代器是廉价的操作。当您为自己的容器设计迭代器时,应尽量使它们紧凑且便于复制。

如何使迭代器更轻量化的示例

如果您需要一个迭代器来存储 std::stack,那么可能出现了问题,但您可能能够消除它。例如,假设您的迭代器在树中执行深度优先遍历。如果该图中的节点没有指向其父节点的指针,那么迭代器需要保持一个父节点的堆栈,以便可以返回上去。但是,如果每个节点都存储指向其父节点的指针,那么迭代器就不需要父节点堆栈了。此外,我们可以查看返回到父节点时来自哪个方向,并确定下一个要去的子节点。这样,我们不需要跟踪每个堆栈帧中的位置。

如果这些方法都不起作用,您仍然可以在没有任何迭代器的情况下遍历数据结构,例如编写一个接受执行某些操作的函数对象的 for_each 函数。

总结一下:

  • 在数据结构中保留更多状态
  • 使遍历更智能化(即在 operator++ 中添加更多逻辑)
  • 在某些情况下完全避免使用迭代器
英文:

No, an Iterator does not have to be trivially copyable:

> A type X meets the Cpp17Iterator requirements if:
> - X meets the Cpp17CopyConstructible, Cpp17CopyAssignable, Cpp17Swappable, and Cpp17Destructible requirements ([utility.arg.requirements], [swappable.requirements]), and
> - iterator_traits&lt;X&gt;​::​difference_type is a signed integer type or void, and
> - the expressions in Table 86 are valid and have the indicated semantics.

- [iterator.iterators] §2

The C++20 iterator concepts also don't impose such a requirement.

Iterators are not trivially copyable, but should be lightweight

However, an iterator is generally supposed to be as lightweight as possible, and algorithms will assume that copying an iterator is a cheap operation. When you are designing iterators for your own container, you should make them as compact and cheap to copy as you can.

Example of how to make an iterator more lightweight

If you need an iterator to store a std::stack, something has probably gone wrong, but you might be able to eliminate it. For example, say your iterator performs depth-first traversal in a tree. If nodes in this graph don't have a pointer to their parent node, you need the iterator to keep a stack of parents so that it can go back up. However, if each node stores a pointer to its parent, the iterator doesn't need a parent stack. Also, we can look at the direction where we came from when going back up to a parent, and determine which child to go to next. This way, we don't need to track our own position within each stack frame.

If none of these things work, you could still traverse the data structure without any iterators, e.g. by writing a for_each function which accepts a function object that does something for every element.

In summary:

  • keep more state in your data structure
  • make traversal more intelligent (i.e. more logic in operator++)
  • avoid use of iterators in some cases completely

huangapple
  • 本文由 发表于 2023年6月14日 23:34:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76475263.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定