27

以下のスニペットは、から 3 つの整数を読み取りますstd::cin。2 を書き込みnumbers、3 番目を破棄します。

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

コードが から正確に 2 つの整数を読み取ることを期待してstd::cinいましたが、これは標準に準拠した正しい動作であることがわかりました。これは標準の見落としですか?この行動の根拠は何ですか?


C++03 標準の 24.5.1/1 から:

構築後、++ が使用されるたびに、反復子は の値を読み取って格納しますT

したがって、上記のコードでは、呼び出し時点でストリーム イテレータが既に 1 つの整数を読み取っています。その時点以降、アルゴリズムの反復子によるすべての読み取りは先読みであり、前の読み取りからキャッシュされた値が生成されます。

次の標準であるn3225の最新のドラフトは、ここで何の変更も受けていないようです (24.6.1/1)。

関連する注意事項として、コンストラクターに関する現在の標準の 24.5.1.1/2 はistream_iterator(istream_type& s)読み取ります

効果: で初期化in_streamsます。value構築中または最初に参照されたときに初期化される場合があります。

「初期化する必要がある」とは対照的に、「初期化される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 個の割り当て。

ご覧のとおり、反復子で正確に何が起こるかは指定されていません。つまり、実装に依存します。

私の意見では、あなたの例は 3 番目の値を読み取るべきではありません。つまり、これは動作を指定していない標準の小さな欠陥です。

于 2013-03-13T12:46:30.133 に答える
1

正確な根拠はわかりませんが、イテレータも operator*() をサポートする必要があるため、読み取った値をキャッシュする必要があります。イテレータが構築時に最初の値をキャッシュできるようにすると、これが簡素化されます。また、ストリームが最初に空のときにストリームの終わりを検出するのにも役立ちます。

あなたのユースケースは、委員会が考慮しなかったものではないでしょうか?

于 2011-02-22T19:03:44.087 に答える
0

今日、あなたから 9 年後、私は同じ問題に陥りました. このスレッドをたどり、問題をいじっていると、これに気付きました. 1回目以降、読み取りごとにイテレータを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;
   }

そして、それは次のような出力を生成するはずです:

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 に答える