为什么std :: copy_n没有递增输入迭代器n次?

时间:2014-04-25 19:10:04

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

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

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

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

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

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

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);
BTW,如果不清楚,我试图避免将数据复制到缓冲区然后再次进入字符串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(); });

3 个答案:

答案 0 :(得分:7)

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&lt; n ,执行*(result + i) = *(first + i)

     

6 返回: result + n

     

7 复杂性:完全n作业。


我不确定它是否适用于InputIterators(没有多通道),因为它不会修改原始迭代器,但总是会推进原始迭代器的副本。它似乎也没有效率。

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

  

表达:++r
  退货类型:X&
  pre:r是不可分辨的   帖子:r可以取消引用或r是过去的结尾   post:不再需要r之前值的任何副本   可解除引用或属于==

的范围

据我所见,<{p>}中的a

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

不再需要可递增。


相关缺陷报告:LWG 2173

答案 1 :(得分:7)

许多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那样,但是{{1}仍然表现得这样。

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

istream_iterators

答案 2 :(得分:2)

源迭代器不是通过引用获取的。因此,它的副本增加n次,但参数保持不变。

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

就具体增加InputIterators的副作用而言,我认为正式,输入迭代器应在每次读取时“递增”(没有增量的重复读取产生相同的值) 。所以,只需将增量设为无操作。