【问题标题】:Why does std::copy_n not increment the Input iterator n times?为什么 std::copy_n 不增加输入迭代器 n 次?
【发布时间】:2014-06-11 15:25:34
【问题描述】:

我希望下面的 buf_iter 指向字符 n 字符在它开始的点之后。相反,它指向最后一个读取的字符。为什么是这样?即,如果我在 copy_n 之前和之后执行 in_stream.tellg(),它们的区别不是 n,而是 (n-1)。如果我用in_stream.read 读取n 字符,那么该位置将提高n

std::istreambuf_iterator<char> buf_iter(in_stream);
std::copy_n(buf_iter, n, sym.begin());

我查看了实现,它显然是故意这样做的,跳过了最后的增量。

另一篇帖子here 提到当从迭代器连接到cin 时递增它会导致读取次数过多,因为读取是在operator++() 上完成的。这听起来像是cin 的问题——为什么没有在operator*() 上完成读取?

标准是否在任何地方指定了这一点?我看到的文档没有提到 from 迭代器会发生什么,而且我看到了两个不同的页面,它们提供了执行每种行为的“可能的正确实现”:

At cppreference we have:

template< class InputIt, class Size, class OutputIt>
OutputIt copy_n(InputIt first, Size count, OutputIt result)
{
    if (count > 0) {
        *result++ = *first;
        for (Size i = 1; i < count; ++i) {
            *result++ = *++first;
        }
    }
    return result;
}

while at cplusplus.com we have:

template<class InputIterator, class Size, class OutputIterator>
  OutputIterator copy_n (InputIterator first, Size n, OutputIterator result)
{
  while (n>0) {
    *result = *first;
    ++result; ++first;
    --n;
  }
  return result;
}

两者都不读取并在结果中产生相同的内容。但是,第一个只会将“第一个”迭代器增加n-1 次,而第二个会将它增加n 次。

什么给了?如何编写可移植代码?我可以使用tellg,然后使用seekg,但我还不如手动循环(啊!)。


请注意,我不是在调用copy_n 后尝试从迭代器中读取,而是在调用copy_n 后从底层流中读取,问题是copy_n 指向短字节在我预期的地方。现在我要使用有点可怕但显然是便携的:

auto pos = in_stream.tellg();
std::istreambuf_iterator<char> buf_iter(in_stream);
std::copy_n(buf_iter, cl, sym.begin());

in_stream.seekg(pos + cl);

uint64_t foo;
in_stream.read(reinterpret_cast<char *>(&foo), 8);

顺便说一句,如果不清楚,我会尽量避免将数据复制到缓冲区中,然后再复制到字符串sym


@DaveS:离开我的具体问题,这里有一个简单的程序,由于输入迭代器最后一次没有递增,所以它没有输出我所期望的:

#include <algorithm>
#include <string>
#include <iostream>
#include <fstream>

int main(int argc, const char * argv[])
{
    std::ifstream in("numbers.txt");

    std::istreambuf_iterator<char> in_iter(in);
    std::ostreambuf_iterator<char> out_iter(std::cout);

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    std::copy_n(in_iter, 3, out_iter);
    std::cout << std::endl;

    return 0;
}

输入文件就是"0123456789\n"

我明白了:

012
234
456

由于istreambuf_iterator::operator++() 的副作用,如果实现copy_n 以增加输入迭代器n 倍,这将给出不同的结果。


@aschepler:需要捕获本地参数,但我会用它:

 std::generate_n(sym.begin(), cl, [&in_stream](){ return in_stream.get(); });

【问题讨论】:

标签: c++ algorithm c++11 stl


【解决方案1】:

许多std::copy_n 实现增加n-1 倍的原因是由于与istream_iterator 的交互,以及它通常是如何实现的。

例如,如果您有一个包含整数的输入文件

std::vector<int> buffer(2);
std::istream_iterator<int> itr(stream); // Assume that stream is an ifstream of the file
std::copy_n(itr, 2, buffer.begin());

因为 istream_iterator 被指定为在递增时读取(以及在构造或第一次取消引用时),如果 std::copy_n 将输入迭代器递增 2 次,您实际上会从文件中读取 3 个值。当copy_n 中的本地迭代器超出范围时,第三个值将被丢弃。

istreambuf_iterator 没有相同的交互,因为它实际上并没有像大多数istream_iterators 那样将值从流中复制到本地副本中,但是copy_n 仍然以这种方式运行。

编辑:如果 copy-N 增加 N 次则丢失数据的示例(cplusplus.com 描述,这似乎不正确)。请注意,这实际上仅适用于 istream_iterators 或其他在增量时读取和删除其基础数据的迭代器。

std::istream_iterator<int> itr(stream); // Reads 1st value

while(n > 0) // N = 2 loop start 
{       
 *result = *first;
 ++result; ++first; // Reads 2nd value
 --n; // N: 1
 // N = 1 loop start
 *result = *first;
 ++result; ++first; // Reads 3rd value
 --n; // N :0
 // Loop exit
}
return result;

【讨论】:

  • 我不认为这是真的——第一次读取是在增量之前完成的,所以就目前而言,这个实验将导致第一次读取的最后一个字符也是第二次读取的第一个字符读,我想。特别是,在循环中调用 copy_n(input, 1, output) 只会导致它重复将相同的(当前)字符写入输出,并且永远不会在输入迭代器上调用 operator++()
  • @dyp:这样的原因是我个人避免使用istream_iteratoristreambuf_iterator 的原因,除非我正在读取整个文件/缓冲区。我发现尝试在文件上拟合迭代器的抽象会让我在最简单的情况下遇到麻烦。
  • @dyp: And here's an update 这表明如果你增加 N 次,它会读取太多。
  • 对我来说,底线是这违反了最小意外原则,因此看起来像是一个错误,无论是在规范还是实现中,我都不在乎。用户不必关心何时发生读取的细节——这就是抽象的意义所在。 :)
  • @sfjac:这不那么令人惊讶:传递 istream_iterator 来复制 N 并让它从输入中删除 N+1 个项目(永久丢失一个),或者传递一个 istreambuf_iterator 迭代器并具有它会在缓冲区上留下额外的内容以手动删除吗?虽然两者都不完全正确,但其中一个会导致数据丢失。
【解决方案2】:

n3797 [algorithms.general]/12

在算法的描述中,运算符+- 用于一些不需要定义的迭代器类别。在这些情况下,a+n 的语义与

X tmp = a;
advance(tmp, n);
return tmp;

b-a的和@的一样

return distance(a, b);

[alg.modifying.operations]

template<class InputIterator, class Size, class OutputIterator>
OutputIterator copy_n(InputIterator first, Size n,
                      OutputIterator result);

5效果:对每个非负整数i ,执行*(result + i) = *(first + i)

6返回: result + n.

7复杂性: 完全是n 分配。


我不确定这对于 InputIterators 是否格式正确(没有多通道),因为它不会修改原始迭代器,但总是推进原始迭代器的副本。好像也没有效率。

[input.iterators]/表 107 - 输入迭代器要求(除了 Iterator)

表达式:++r
返回类型:X&amp;
pre: r 是可解引用的。
帖子:r 是可取消引用的,或者 r 是过去的。
post: 之前的 r 值的任何副本都不再需要 可取消引用或位于== 的域中。

据我所知,a in

X tmp = a;
advance(tmp, n);
return tmp;

因此不再需要是可递增的。


相关缺陷报告:LWG 2173

【讨论】:

  • 我同意这很奇怪,应该是库缺陷。其他几个算法需求也有同样的问题。
  • 虽然我完全同意观察结果,但我看不出这与问题有何关系。 std::copy_n 确实使输入迭代器(以及您可能拥有的任何备份副本)无效。它似乎是一个黑洞。但事实是operator++ 的调用应该被认为是可观察的行为(你可以传递你自己的)。
  • @MSalters 不确定您的批评是什么。我认为copy_n 的规范与对operator++ 的调用有关。因此,究竟应该是 N 次调用还是 N-1 次调用并不能真正得到回答,因为没有实现者可能从字面上使用该规范。如果他们这样做了,对于i == n-1first 将增加n-1 次,之后没有增加。因此我怀疑正确答案是N-1,但这取决于规范是如何固定的。
  • gcc 在某一时刻改变了对 copy_n 的看法:gcc.gnu.org/bugzilla/show_bug.cgi?id=50119
【解决方案3】:

源迭代器不被引用。因此,它的副本增加了 n 次,但参数保持不变。

10 次中有 9 次,这就是你想要的。

就在 InputIterators 上增加的副作用而言,我认为正式地,输入迭代器应在每次读取时“增加”(重复读取而不增加 not 会产生相同的值) .因此,只需将增量设为无操作即可。

【讨论】:

  • 虽然这是真的,但istreambuf_iterator 类型具有浅拷贝语义。问题不是“为什么迭代器没有更新?”就像“为什么迭代器更新了 n - 1 次而不是 n 次?”
  • @templatetypedef 我的回答的后半部分对你有意义吗? (我会看看我是否能找到一个相当权威的来源,但从概念上讲它有点道理)。而且我认为 InputIterators 没有“浅拷贝语义”。它们具有不确定的结果,这正是您对 InputIterator 概念所期望的。 (迭代器是按值,但"you can't step into the same stream a second time"
  • @sehe:一般来说,您不能使用输入迭代器的旧位置的副本,但 std::istreambuf_iterator 被明确定义为允许副本指向同一个缓冲区,执行 @987654324 @ 递增,sgetc 取消引用。
  • 我不认为(repeated read without increment does not yield the same value) 是任何标准指定的输入迭代器的情况。即使istream_iterator 会重复返回相同的值,直到你增加为止。
  • @DaveS 迭代器不是必需返回不同的值,但它是允许用于 InputIterator。更强的概念是 ForwardIterator(它显式允许重新读取迭代器,即使副本已被推进)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
  • 2016-04-16
  • 1970-01-01
  • 2020-09-13
  • 1970-01-01
  • 2012-11-24
  • 1970-01-01
相关资源
最近更新 更多