【问题标题】:Reversing an on-demand iterator反转按需迭代器
【发布时间】:2014-01-13 23:50:39
【问题描述】:

我有一个迭代器DataIterator,它按需生成值,因此取消引用运算符返回一个数据,而不是数据&。我认为这是一件好事,直到我尝试通过将数据 DataIterator 包装在 reverse_iterator 中来反转数据。

DataCollection collection

std::reverse_iterator<DataIterator> rBegin(iter) //iter is a DataIterator that's part-way through the collection
std::reverse_iterator<DataIterator> rEnd(collection.cbegin());

auto Found = std::find_if(
    rBegin, 
    rEnd,
    [](const Data& candidate){
        return candidate.Value() == 0x00;
});

当我运行上面的代码时,它永远不会找到值等于 0 的 Data 对象,即使我知道存在一个。当我在谓词中插入断点时,我会看到奇怪的值,这些值我从未想过会看到,例如 0xCCCC - 可能是未初始化的内存。发生的情况是 reverse_iterator 的取消引用运算符看起来像这样(来自 xutility - Visual Studio 2010)

Data& operator*() const
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp); //Here's the problem - the * operator on DataIterator returns a value instead of a reference
}

最后一行是问题所在 - 创建一个临时数据并返回对该数据的引用。引用立即无效。

如果我将 std::find_if 中的谓词更改为采用 (Data Candidate) 而不是 (const Data& Candidate),则该谓词有效 - 但我很确定我只是对那里未定义的行为感到幸运。引用无效,但我会在内存被破坏之前制作数据副本。

我能做什么?

  1. 修复我的 DataIterator 以使 operator* 返回 Data& 而不是 Data?我真的不明白这怎么可能。我的 DataIterator 返回 Data 而不是 Data& 的全部意义在于我没有空间将整个未压缩数据集保存在内存中,因此我创建了您想要按需查看的项目。 也许我可以保留“当前”数据值 - 但是当您增加或减少 DataIterator 时,该引用将变得无效。 编辑 one of the answers suggests a shared_ptr
  2. 编写一个reverse_iterator 的特化并使其取消引用运算符返回一个值而不是引用?这似乎是一项令人沮丧的工作,但可以理解,因为我的 DataIterator 在这里表现不佳 - 而不是 STL 的其余部分。
  3. 按照同样的思路,也许可以创建一个反向的 find_if - 可能比专门化 reverse_iterator 工作更少。
  4. 其他我没想到的东西

我可以对 DataIterator 做些什么来防止其他人在 6 个月后尝试同样的事情时花半天时间找出问题所在?

【问题讨论】:

  • 为什么要为这个任务选择迭代器?
  • 将数据复制到vector&lt;Data&gt;,并在该向量上使用reverse_iterator
  • 选择了一个迭代器,因为底层数据非常非常大,但是被压缩了。迭代器解压缩数据,在您迭代数据时一次一个项目。这让我们可以查看数据集,并懒惰地创建派生数据集,而无需在内存中保留多个非常大的数据集。
  • std::reverse_iterator 要求其参数是双向迭代器 (C++11§[reverse.iter.requirements]/1)。您的迭代器充其量是一个输入迭代器,因为即使是低级的 Forward 迭代器也需要 operator* 才能评估为 value_type&amp; ([forward.iterators]/1)。您必须编写自己的专业化 reverse_iterator
  • @PeteBaughman 这无疑是标准库中最严重的设计缺陷之一,就在 iostream 中将 I/O、格式化、国家化和字符编码耦合在一起。 Boost 的人在不久前做了很多工作来分离迭代器中的遍历和访问的概念(New Iterator Concepts),甚至有一些语言委员会的提案文件,但我认为它没有任何结果。

标签: c++ stl iterator reverse-iterator


【解决方案1】:

并不是说我非常喜欢这个想法,但是如果你堆分配了一个 Data 对象,然后将一个引用返回给一个 shared_ptr 给它,那么如果需要,并且让您在前进时“忘记”它。

另一方面,实现自己的原生 reverse_iterator 可能是一个更大的胜利。这就是我为自己的链表所做的,因为我没有使用像gcc 这样的哨兵对象,也不能使用std::reverse_iterator。真的没那么难。

【讨论】:

  • 对 shared_ptr 的引用是否使其保持活动状态?无论如何 - 如果我重新开始,那么某种类型的引用计数可能是要走的路。不幸的是,我认为现在对现有代码库的改动太大了。
  • shared_ptr 的引用本身不会使其保持活动状态,但如果有人想使用它,他们可能会将其复制到另一个shared_ptr 中。这将使它保持活力。
  • 知道了——我脑子里缺少的步骤是将 shared_ptr 缓存在迭代器的私有字段中。当您移动迭代器时,私有字段会被清除,如果其他人在此期间抓取了一个副本,则底层数据仍然存在
  • @woolstar 返回shared_ptr 时,您应该始终按值返回它,而不是作为参考。
【解决方案2】:

这是因为reverse_iterator接口是在decltype存在之前设计的。今天,那将被写成

auto operator*() const -> decltype(*current)
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp);
}

in C++14,甚至不需要尾随返回类型,因为它可以被推断出来。

decltype(auto) operator*() const
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp);
}

【讨论】:

  • 天哪.. C++14 代码看起来很整洁。但是,我不确定我会对此感到满意。我的意思是,我认为它太像 C# 和 javascript 中的 var 了。只是看到我喜欢的类型。虽然很方便,而且我第一次看到这个。来自我的 +1。
【解决方案3】:

我最终在 cmets 中接受了凯西的建议。他没有将其发布为我可以接受的答案,因此我将自己写出来。

我对 DataIterator 的 reverse_iterator 进行了专门化,它返回一个值而不是一个引用。这涉及从 xutility 复制/粘贴实现,将模板参数之一指定为 DataIterator,并更改

reference operator*() const

value operator*() const

【讨论】:

    猜你喜欢
    • 2010-10-27
    • 2012-05-09
    • 2020-05-21
    • 2017-11-10
    • 1970-01-01
    • 2018-09-23
    • 2011-01-03
    • 2020-11-06
    相关资源
    最近更新 更多