返回多于 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&lt;T&gt;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&lt;T&gt;getInternalIndex1D()getCoordinate()getValue() 是我一味命名的。

在实际案例中,我创建了许多自定义数据结构,它们会生成如此奇怪的迭代器。

问题

T&amp;std::pair 不够时,是否有std::/iterator::operator*() 的标准返回值类型?

我已经怀疑这个问题好几个月了,但很不愿意问。 如果这个问题需要改进,请评论。

编辑

(澄清这个词标准化 - 这部分是我的主观概念。)

我觉得大多数语言中的所有类型的标准集合,例如:-

java.util.Collection/ArrayList 在 Java 中 C++ 中的std::unordered_map/vector

它们的迭代器都具有函数 getValue()operator* 的签名,它们返回 TStandardPair&lt;index,T&gt; - 没有其他类型。

多年来,我坚信这是我真正应该遵守的规则或强烈的惯例/传统。

一定是有原因的。 但是,我不确定它是什么,因为我可能对 C++ 很陌生。

如果我设计我的软件返回一些奇怪的东西(例如上面例子中的Bundle3D&lt;T&gt;),我想我会受到非常规设计的严厉惩罚。 (例如没有.first 字段)

【问题讨论】:

永远不要不愿意问。你能告诉我你所说的“标准化”是什么意思吗?如果您寻求与标准库算法的一致性,那么您实现迭代器的事实已经是标准的。 @StoryTeller 感谢您让我振作起来。 :) 我已经编辑了问题以澄清这个词。 如果您的容器的行为类似于std::map&lt;std::tuple&lt;int,int,int&gt;,T&gt;,那么迭代器可能也应该看起来相同。 【参考方案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 &amp;,因为变异键可能会破坏地图 我将假设一种可能的情况:我计划 Bundle 仅存储 index1D 因为大多数循环范围只想访问一维索引。 getCoordinate() 很少使用,因此允许使用昂贵的算法(例如转换 1D->3D)。如果它变成那样,我应该把它作为一个函数,对吧?也就是说,这真的取决于我将来打算用它做什么。 ? .... 我认为 bundle/function 是最安全的路径。【参考方案2】:

我将假设您的 Map3D 是价值容器的空间坐标。

我会返回一个std::pair&lt;const Vector3Int, T&gt;&amp;,并且不会将 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)的工作方式类似于Map3Dinternal1DIndex 使我能够致电 map3D2.setIndexInternalHack(internal1DIndex,key3D,value) 以获得一些性能提升。我不想失去这个优势。你碰巧知道保留它的方法吗? 是的,但是在您的解决方案中,我无法从 ele 内部的 for(auto&amp; ele:map) 访问。 好的。没有什么可以阻止您使用不同的Map3D::value_type。我已编辑以包含内部索引,您可能希望将其扩展为包含 const Vector3Int &amp; first = get&lt;0&gt;();T &amp; second = get&lt;1&gt;(); 以便看起来更像一对

以上是关于返回多于 T& 和 std::pair 的 iterator::operator* 的标准接口的主要内容,如果未能解决你的问题,请参考以下文章

返回 std::pair 与通过非常量引用传递

没有函数模板“std::make_pair”的实例与参数列表匹配

漂亮的打印 std::tuple

std::map的insert和下标[]操作区别

List<> 容量返回的项目多于添加的项目

std :: make_pair:无法将'ch'(类型'char')转换为'char &&'[duplicate]