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

sfj*_*jac 20 c++ algorithm stl c++11

我希望以下内容可以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());
Run Code Online (Sandbox Code Playgroud)

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

这里的另一篇文章提到,当迭代器连接起来时,从迭代器开始递增cin会导致读取次数过多而导致读取次数过多operator++().这听起来像是一个问题cin- 为什么不进行读取operator*()?

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

在cppreference我们有:

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;
}
Run Code Online (Sandbox Code Playgroud)

在cplusplus.com我们有:

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;
}
Run Code Online (Sandbox Code Playgroud)

两者都读取并导致结果中的相同内容.但是,第一个只会增加"第一个"迭代器的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);
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果不清楚,我试图避免将数据复制到缓冲区然后再次进入字符串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;
}
Run Code Online (Sandbox Code Playgroud)

输入文件就是 "0123456789\n"

我越来越:

012
234
456
Run Code Online (Sandbox Code Playgroud)

由于副作用istreambuf_iterator::operator++(),如果copy_n实现增加输入迭代器n时间,则会产生不同的结果.


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

 std::generate_n(sym.begin(), cl, [&in_stream](){ return in_stream.get(); });
Run Code Online (Sandbox Code Playgroud)

dyp*_*dyp 7

n3797 [algorithms.general]/12

在运营商的算法的描述+和-用于一些为它们不具有要定义的迭代器的类别.在这些情况下,语义与.的语义a+n相同

X tmp = a;
advance(tmp, n);
return tmp;
Run Code Online (Sandbox Code Playgroud)

和那个b-a是一样的

return distance(a, b);
Run Code Online (Sandbox Code Playgroud)

[alg.modifying.operations]

template<class InputIterator, class Size, class OutputIterator>
OutputIterator copy_n(InputIterator first, Size n,
                      OutputIterator result);
Run Code Online (Sandbox Code Playgroud)

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

6 返回: result + n.

7 复杂性:完全n分配.


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

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

表达式:++r
返回类型:X&
pre:r是dereferencable.
post:r是可解除引用的,或者r是过去的结尾.
post:r不再要求任何前一个值的副本可以解除引用或属于==.

据我所见,a中

X tmp = a;
advance(tmp, n);
return tmp;
Run Code Online (Sandbox Code Playgroud)

因此不再需要增量.


相关缺陷报告:LWG 2173


Dav*_*e S 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());
Run Code Online (Sandbox Code Playgroud)

因为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;
Run Code Online (Sandbox Code Playgroud)

  • 对我来说,最重要的是这违反了最少惊喜的原则,因此看起来像一个bug,无论是在规范还是实现中,我都不在乎.用户不必关心读取何时发生的细节 - 这就是抽象的关键.:) (2认同)

seh*_*ehe 3

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

十分之九,这就是你想要的。

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

  • 虽然这是事实,但“istreambuf_iterator”类型具有浅复制语义。问题不是“为什么迭代器没有更新?” 就像“为什么迭代器更新了 n - 1 次而不是 n 次?” (2认同)