返回多于 T& 和 std::pair 的 iterator::operator* 的标准接口
Posted
技术标签:
【中文标题】返回多于 T& 和 std::pair 的 iterator::operator* 的标准接口【英文标题】:standard interface of iterator::operator* that return more than T& and std::pair 【发布时间】:2017-05-10 09:06:02 【问题描述】:每个数据结构的迭代器都有不同类型的返回值operator*()
,例如:-
DataStructure<T>
:返回T&
。
std::unordered_map<K,T>
:返回std::pair<const K, T>&
。
const std::unordered_map<K,T>
:返回const std::pair<K, T>&
。
如果我的iterator::operator*()
想要返回更复杂的东西(std::pair
还不够),数据类型应该是什么?
示例
我有一个自定义的 3D 数据结构 Map3D<T>
,它就像一张地图 (int,int,int)->T
。
为了实现上述要求,我新建了一个类Bundle3D<T>
。
template<class T>class Bundle3D
//getInternalIndex1D()
//getCoordinate()
//getValue()
;
Map3D<T>
的 iterator::operator*
的签名如下:-
Bundle3D<T> iterator::operator*() ...
它的用法是这样的:-
Map3D<float> map;
for(auto& ele:map) //"ele" has some special type named "Bundle"
int keyInternal1DIndex=ele.getInternalIndex1D();
Vector3Int& key=ele.getCoordinate();
float& value=ele.getValue();
效果很好,但我认为我的代码没有标准化。
也就是说,Bundle3D<T>
、getInternalIndex1D()
、getCoordinate()
和getValue()
是我一味命名的。
在实际案例中,我创建了许多自定义数据结构,它们会生成如此奇怪的迭代器。
问题
当T&
和std::pair
不够时,是否有std::
/iterator::operator*()
的标准返回值类型?
我已经怀疑这个问题好几个月了,但很不愿意问。 如果这个问题需要改进,请评论。
编辑
(澄清这个词标准化 - 这部分是我的主观概念。)
我觉得大多数语言中的所有类型的标准集合,例如:-
java.util.Collection/ArrayList
在 Java 中
C++ 中的std::unordered_map/vector
它们的迭代器都具有函数 getValue()
或 operator*
的签名,它们返回 T
或 StandardPair<index,T>
- 没有其他类型。
多年来,我坚信这是我真正应该遵守的规则或强烈的惯例/传统。
一定是有原因的。 但是,我不确定它是什么,因为我可能对 C++ 很陌生。
如果我设计我的软件返回一些奇怪的东西(例如上面例子中的Bundle3D<T>
),我想我会受到非常规设计的严厉惩罚。 (例如没有.first
字段)
【问题讨论】:
永远不要不愿意问。你能告诉我你所说的“标准化”是什么意思吗?如果您寻求与标准库算法的一致性,那么您实现迭代器的事实已经是标准的。 @StoryTeller 感谢您让我振作起来。 :) 我已经编辑了问题以澄清这个词。 如果您的容器的行为类似于std::map<std::tuple<int,int,int>,T>
,那么迭代器可能也应该看起来相同。
【参考方案1】:
你现在拥有的一切都还好。我只想指定一件我认为不是很 C++ 的东西,实际上可能会损害您将来的标准化选项。
这是吸气剂。显然你在 Java 中有很深的根基,而公共成员的概念在 Java 编程中是令人厌恶的,以至于存在“bean”的概念。我不打算在这里敲Java,它是一种很好的语言,有它自己的好习惯。
但这是 C++,有它自己的编程习惯。您显然注意到了对std::pair
内容的直接访问。这是有原因的。一对只是打包在一起的两个项目,这是其行为的总和。吸气剂只会妨碍。 :)
不需要它们,因为我们不是在处理捆绑在一起的两个项目的“抽象”,而是我们确实有一个具体的、诚实的、捆绑1。
很明显,我们可以捆绑两个以上的物品。这就是为什么我们有std::tuple
。现在,虽然对元组的所有访问都是通过一个名为get
的非成员函数来实现的,但这仅仅是因为无法为任意大小的包的成员命名。 get
仍然返回对元素的引用,因此您可以直接访问包。
这个冗长的阐述很重要,因为即将推出的名为"Structured Bindings" 的 C++1z 功能依赖于元组和对的标准行为,以及 C++ 程序员如何看待聚合。在此功能下,对地图的迭代将如下所示:
#include <map>
#include <iostream>
std::map<char const*, int> foo()
return
"foo", 3 ,
"bar", 7 ,
"baz", 1 ,
;
int main()
for (auto [key, val] : foo())
std:: cout << "( " << key << ", " << val << " )\n";
return 0;
Live example
这同样适用于任何用户定义的数据包。如果成员是公开的,您自己在帖子中的返回值类型可以类似地绑定:
struct Vector3Int ;
template<class T>
struct Bundle3D
int internal_index_id;
Vector3Int const &coord;
T &value;
;
int main()
Vector3Int vec;
float val;
Bundle3D<float> bundle 1, vec, val ;
auto[ idx_id, coord, value] = bundle;
// coord = ; // compile error since coord gets the cv qualifer
Live example
所以我的建议是,将 getter 留给您的 Java 代码,并将聚合用作 C++ 的数据包。
1 一个聚合,更正式。
【讨论】:
为什么在 C++ 中直接访问字段比在 Java 中更好。我正在浏览***.com/questions/1568091/why-use-getters-and-setters、***.com/questions/565095/… 和***.com/questions/737409/… 仍然不知道。 ....我在暴露公共成员时有不好的经历,例如***.com/questions/42885210/…. @javaLover - 是的,错字(我在发布之前已经阅读过,但选择性失明:))。至于为什么我们更喜欢直接访问,好吧,这一切都是为了意识到这里真的没有封装。我们只想退货两件,何必这么麻烦呢?无论如何,这是我的看法。 @javaLover - 关于你的糟糕经历,我只能说这是一个判断问题。有时直接访问确实很糟糕,为了将来的验证,我们不应该这样做。但这取决于对象的责任,如果它包含逻辑,而不仅仅是数据。对于一个简单的聚合,职责只是“将这些任意值一起保存在内存中”。所以直接访问就足够了。coord
真的应该是const Vector3Int &
,因为变异键可能会破坏地图
我将假设一种可能的情况:我计划 Bundle
仅存储 index1D
因为大多数循环范围只想访问一维索引。 getCoordinate()
很少使用,因此允许使用昂贵的算法(例如转换 1D->3D)。如果它变成那样,我应该把它作为一个函数,对吧?也就是说,这真的取决于我将来打算用它做什么。 ? .... 我认为 bundle/function 是最安全的路径。【参考方案2】:
我将假设您的 Map3D
是价值容器的空间坐标。
我会返回一个std::pair<const Vector3Int, T>&
,并且不会将 getInternalIndex1D 作为返回的一部分。这可以保留为函数 Vector3Int -> int。
查看UnorderedAssociativeContainer 了解对定义有用的成员类型
例如
template <typename T>
class Map3D
using key_type = Vector3Int;
using mapped_type = T;
using value_type = Bundle3D<T>; // Key, Value, Internal1DIndex
using reference = Bundle3D<T> &;
using pointer = Bundle3D<T> *;
using hasher = /*Function object that calculates Internal1DIndex*/
using key_equal = std::equal<Vector3Int>
... iterators, const_* definitions, etc
【讨论】:
是的,它是价值容器的空间坐标。将“const Vector3Int”转换为 int 会花费一点,所以我不愿意省略它。顺便说一句,这是一个很好的答案,谢谢。 我不会将 Internal1DIndex 暴露给外部代码,只需使用iterator
的实例来表示内部位置
好点。从我的情况来看,还有其他数据结构(例如命名为Map3D2
)的工作方式类似于Map3D
。 internal1DIndex
使我能够致电 map3D2.setIndexInternalHack(internal1DIndex,key3D,value)
以获得一些性能提升。我不想失去这个优势。你碰巧知道保留它的方法吗?
是的,但是在您的解决方案中,我无法从 ele
内部的 for(auto& ele:map)
访问。
好的。没有什么可以阻止您使用不同的Map3D::value_type
。我已编辑以包含内部索引,您可能希望将其扩展为包含 const Vector3Int & first = get<0>();
和 T & second = get<1>();
以便看起来更像一对以上是关于返回多于 T& 和 std::pair 的 iterator::operator* 的标准接口的主要内容,如果未能解决你的问题,请参考以下文章