27

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 开始:

构造完成后,每次使用 ++ 时,迭代器都会读取并存储T.

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

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

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

in_stream效果:用 初始化svalue可以在构造期间或第一次被引用时初始化。

强调“value 可能被初始化......”而不是“被初始化”。这听起来与 24.5.1/1 相矛盾,但也许这值得自己提出一个问题。

4

4 回答 4

12

不幸的是,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
*/
于 2011-02-26T23:31:50.243 回答
5

今天我遇到了非常相似的问题,这里是例子:

#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 < n,执行 *(result + i) = *(first + i)。
返回:结果 + n。
复杂性:恰好 n 个任务。

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

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

于 2013-03-13T12:46:30.133 回答
1

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

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

于 2011-02-22T19:03:44.087 回答
0

今天,在你之后 9 年,我陷入了同样的问题,所以跟着这个线程,在玩这个问题的时候注意到了这一点,似乎我们可以在第一次阅读之后为每次阅读走一步迭代器(我的意思是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;
   }

这应该产生如下输出:

1 2 3 4 5
And for the rest of the stream
6 7 8 9 10 11 12 13 14 15
于 2020-04-13T14:18:43.530 回答