的std :: istream_iterator<>与copy_n()和朋友

时间:2011-02-22 04:49:23

标签: c++ istream-iterator

下面的代码段从std::cin读取三个整数;它将两个写入numbers并丢弃第三个:

std::vector<int> numbers(2);
copy_n(std::istream_iterator<int>(std::cin), 2, numbers.begin());

我希望代码能够从std::cin精确读取两个整数,但事实证明这是一个正确的,符合标准的行为。这是标准中的疏忽吗?这种行为的理由是什么?


C ++ 03标准中的24.5.1 / 1:

  

构建之后,每一个   使用time ++,迭代器读取   并存储值T

所以在上面的代码中,在调用时,流迭代器已经读取了一个整数。从那时起,算法中迭代器的每次读取都是预读,产生从前一次读取缓存的值。

下一个标准n3225的最新草案似乎没有任何变化(24.6.1 / 1)。

在相关的说明中,参考istream_iterator(istream_type& s)构造函数的当前标准的24.5.1.1/2读取

  

效果:使用初始化in_stream   svalue可能会在此期间初始化   建设或第一次   引用。

强调“value 可以初始化......”而不是“ 初始化”。这听起来与24.5.1 / 1相矛盾,但也许这应该是一个自己的问题。

4 个答案:

答案 0 :(得分:10)

不幸的是,copy_n的实现者未能解释复制循环中的预读。 Visual C ++实现在stringstream和std :: cin上都能正常工作。我还检查了原始示例中的情况,其中istream_iterator是在行中构造的。

以下是STL实现的关键代码。

template<class _InIt,
    class _Diff,
    class _OutIt> inline
    _OutIt _Copy_n(_InIt _First, _Diff _Count,
        _OutIt _Dest, input_iterator_tag)
    {   // copy [_First, _First + _Count) to [_Dest, ...), arbitrary input
    *_Dest = *_First;   // 0 < _Count has been guaranteed
    while (0 < --_Count)
        *++_Dest = *++_First;
    return (++_Dest);
    }

这是测试代码

#include <iostream>
#include <istream>
#include <sstream>
#include <vector>
#include <iterator>

int _tmain(int argc, _TCHAR* argv[])
{
    std::stringstream ss;
    ss << 1 << ' ' << 2 << ' ' << 3 << ' ' << 4 << std::endl;
    ss.seekg(0);
    std::vector<int> numbers(2);
    std::istream_iterator<int> ii(ss);
    std::cout << *ii << std::endl;  // shows that read ahead happened.
    std::copy_n(ii, 2, numbers.begin());
    int i = 0;
    ss >> i;
    std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;

    std::istream_iterator<int> ii2(std::cin);
    std::cout << *ii2 << std::endl;  // shows that read ahead happened.
    std::copy_n(ii2, 2, numbers.begin());
    std::cin >> i;
    std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;

    return 0;
}


/* Output
1
1 2 3
4 5 6
4
4 5 6
*/

答案 1 :(得分:3)

今天我遇到了非常类似的问题,这是一个例子:

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <string>

struct A
{
    float a[3];
    unsigned short int b[6];
};

void ParseLine( const std::string & line, A & a )
{
    std::stringstream ss( line );

    std::copy_n( std::istream_iterator<float>( ss ), 3, a.a );
    std::copy_n( std::istream_iterator<unsigned short int>( ss ), 6, a.b );
}

void PrintValues( const A & a )
{
    for ( int i =0;i<3;++i)
    {
        std::cout<<a.a[i]<<std::endl;
    }
    for ( int i =0;i<6;++i)
    {
        std::cout<<a.b[i]<<std::endl;
    }
}

int main()
{
    A a;

    const std::string line( "1.1 2.2 3.3  8 7 6 3 2 1" );

    ParseLine( line, a );

    PrintValues( a );
}

用g ++ 4.6.3编译上面的例子产生一个:

1.1 2.2 3.3 7 6 3 2 1 1

,用g ++ 4.7.2编译会产生另一个结果:

1.1 2.2 3.3 8 7 6 3 2 1

c ++ 11标准告诉我copy_n

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

效果:对于每个非负整数i&lt; n,执行*(结果+ i)= *(第一+ i)   返回:结果+ n。
  复杂性:完全是n个任务。

正如您所看到的,没有指定迭代器究竟发生了什么,这意味着它依赖于实现。

我的观点是你的例子不应该读取第3个值,这意味着这是标准中的一个小缺陷,他们没有指定行为。

答案 2 :(得分:1)

我不知道确切的理由,但由于迭代器也必须支持operator *(),因此它必须缓存它读取的值。允许迭代器在构造时缓存第一个值简化了这一点。它还有助于在流最初为空时检测流末尾。

也许您的用例是委员会没有考虑的用例?

答案 3 :(得分:0)

今天,在您之后的9年,我遇到了同样的问题,因此,按照这个主题进行讨论的同时发现这个问题,似乎我们可以在第一次阅读后为每次阅读的内容走一个步骤(我的意思是{{ 1}}也不能自动忽略换行结束,我们借助cin来帮助它,我想我们也可以帮助该实现):

cin.ignore()

并且应该产生如下输出:

    #include<bits/stdc++.h>
    using namespace std;

    int main(){

    freopen("input.txt","r",stdin);

    istream_iterator<int> it(cin);

    ostream_iterator<int> cout_it(cout, " ");

    copy_n(it, 5, cout_it);

    cout<<"\nAnd for the rest of the stream\n";

    for(int i=0;i<10;i++){

        it++;

        copy_n(it, 1, cout_it);

      }

    return 0;
   }