【发布时间】:2018-12-05 09:53:03
【问题描述】:
上下文
我试图实现一个像容器一样的 nD 数组。可以包装底层序列容器并允许将其作为容器的容器(...)处理的东西:arr[i][j][k] 应该是_arr[(((i * dim2) + j) * dim3) + k] 的(最终是 const)引用。
到此为止,arr[i] 只是子数组的包装类...
当我尝试实现交互器时,我突然意识到龙无处不在:
- 我的容器不是符合标准的容器,因为
operator []返回的是代理或包装器,而不是真正的引用 (When Is a Container Not a Container?) - 这会导致迭代器成为 stash 迭代器(已知是错误的(Reference invalidation after applying reverse_iterator on a custom made iterator 及其接受的answer)
- ...或不一定更好的代理迭代器 (To Be or Not to Be (an Iterator))
真正的问题是,一旦你有一个代理容器,没有迭代器可以遵守以下对前向迭代器的要求:
转发迭代器 [forward.iterators]
...
6 如果a和b都是可解引用的,那么a == b当且仅当*a和*b绑定到同一个对象。 p>
示例来自标准库本身:
-
vector<bool>不尊重容器的所有要求,因为它返回的是代理而不是引用:类向量 [vector.bool]
...
3 不要求将数据存储为 bool 值的连续分配。空间优化 建议使用位表示。
4reference 是一个模拟向量中单个位的引用行为的类。 -
文件系统路径迭代器已知是一个隐藏迭代器:
路径迭代器 [fs.path.itr]
...
2 path::iterator 是一个常量迭代器,满足双向迭代器的所有要求 (27.2.6 ) 除了,对于带有a == b的path::iterator 类型的可解引用迭代器a和b,没有要求 那*a和*b绑定到同一个对象。来自cppreference:
注意:std::reverse_iterator 不适用于返回对成员对象的引用的迭代器(所谓的“存储迭代器”)。存储迭代器的一个例子是 std::filesystem::path::iterator。
问题
我目前找到了大量关于为什么代理容器不是真正的容器以及如果标准允许代理容器和迭代器会很好的参考资料。但我仍然不明白什么是最好的,什么是真正的限制。
所以我的问题是为什么代理迭代器真的比存储迭代器更好,以及它们中的任何一个都允许使用哪些算法。如果可能的话,我真的很想为这样的迭代器找到一个 reference 实现
作为参考,我的代码的当前实现已在Code Review 上提交。它包含一个存储迭代器(当我尝试使用 std::reverse_iterator 时立即中断)
【问题讨论】:
-
我知道这是一个 give me ze code 问题,但至少有一些研究,我认为这样的示例实现在 SO 上会很好。
-
请忽略返回引用的前向迭代器要求,并以非常非正式的方式阅读“绑定到同一个对象”。
-
实现什么目的的“最不糟糕的实施”?与任何概念一样,重要的是使用它的代码的期望。只要每个使用它的人知道它是一个代理迭代器并相应地对待它,代理迭代器就没有任何问题。 “存储”迭代器也是如此。这些问题是当您想将它们传递给具有代理/存储迭代器无法满足的期望的算法(例如在标准库中)时。
-
@MarcGlisse:“请忽略返回引用的前向迭代器要求,并以非常非正式的方式阅读“绑定到同一个对象”。“你不能忽略它。它是标准的一部分,接受 ForwardIterators 编写的代码有权执行
value_type_t<Iterator> &val = *it;,其中value_type_t从迭代器中获取值类型。 -
@SergeBallesta:“但我敢打赌,有些人可能会工作......”这有什么好处?你不知道哪个会起作用。你不知道明天哪个会停止工作。你不知道它是否适用于 libc++ 或 libstdc++ 或 msvc 的标准库。请记住:像
auto &val = *it;这样无害的东西会破坏代理迭代器。那么在没有保证的情况下“工作”有什么好处呢?
标签: c++ iterator proxy-pattern