2

ベクトルに対して何らかの操作を実行しようとしています。また、場合によってはベクトルでのみ消去を呼び出します。

ここに私のコードがあります

while(myQueue.size() != 1)
{
    vector<pair<int,int>>::iterator itr = myQueue.begin();
    while(itr != myQueue.end())
    {
        if(itr->first%2 != 0)
            myQueue.erase(itr);
        else
        {
            itr->second = itr->second/2;
            itr++;
        }
    }
}

2回目の反復でクラッシュしています。そして、メッセージ vector iterator incompatible でこのクラッシュが発生しています。

クラッシュの原因は何ですか?

4

4 に答える 4

8

が呼び出された場合erase()、反復子は無効になり、その反復子はループの次の反復でアクセスされます。std::vector::erase()消去されたイテレータの次のイテレータを返します。

itr = myQueue.erase(itr);
于 2012-05-24T12:51:12.900 に答える
2

ベクトルの範囲の開始と終了を過ぎたイテレータ範囲[b, e)が与えられた場合、範囲内のどこかにあるイテレータでの消去操作は、からまでのすべてのイテレータを無効にします。そのため、を呼び出すときは十分に注意する必要があります。メンバーは、後続の操作のために使用できる新しいイテレータを返します。これを使用する必要があります。beiieeraseerase

 itr = myQueue.erase( itr );

別の方法は、要素と最後の要素を交換してiから、最後の要素を消去することです。これを超える要素の移動回数が少なくて済むため、これはより効率的iです。

myQueue.swap( i, myQueue.back() );
myQueue.pop_back();

また、見た目から、なぜ使っているのvectorですか?が必要な場合は、queueを使用することもできますstd::queue

于 2012-05-24T12:53:45.040 に答える
2

それは未定義の動作です。特に、イテレータを消去すると無効になり、何にも使用できなくなります。ループを展開する慣用的な方法は次のようになります。

for ( auto it = v.begin(); it != v.end(); ) {
   if ( it->first % 2 != 0 )
      it = v.erase(it);
   else {
      it->second /= 2;
      ++it;
   }
}

しかし、繰り返しになりますが、独自のループをロールバックせずにアルゴリズムを使用する方が効率的で慣用的です。

v.erase( std::remove_if( v.begin(),
                         v.end(),
                         []( std::pair<int,int> const & p ) {
                             return p.first % 2 != 0;
                       }),
         v.end() );
std::transform( v.begin(), v.end(), v.begin(), 
                []( std::pair<int,int> const & p ) {
                    return std::make_pair(p.first, p.second/2);
                } );

このアプローチの利点は、消去中の要素のコピー数が少なく (範囲内に残っている有効な各要素が 1 回しかコピーされない)、間違いが起こりにくい (つまり、無効化された要素を誤用する) ことです。 iterator...) 欠点はremove_if_and_transform、要素が多数ある場合に効率が低下する可能性がある 2 パス アルゴリズムであるため、存在しないことです。

于 2012-05-24T12:54:29.597 に答える
2

ループを変更しながら繰り返すことは、一般的に注意が必要です。

したがって、非連想シーケンスで使用できる特定の C++ イディオムがあります。それは、erase-remove イディオムです。

remove_ifアルゴリズムの使用とeraseメソッドの範囲オーバーロードを組み合わせます。

myQueue.erase(
    std::remove_if(myQueue.begin(), myQueue.end(), /* predicate */),
    myQueue.end());

ここで、述語は、典型的なファンクター オブジェクトとして、または新しい C++11 ラムダ構文を使用して表現されます。

// Functor
struct OddKey {
    bool operator()(std::pair<int, int> const& p) const {
        return p.first % 2 != 0;
    }
};

/* predicate */ = OddKey()

// Lambda
/* predicate */ = [](std::pair<int, int> const& p) { return p.first % 2 != 0; }

ラムダ形式はより簡潔ですが、自己文書化 (名前なし) が少なく、C++11 でのみ使用できます。好みや制約に応じて、最も適したものを選択してください。


コードの書き方を向上させることができます: Boost.Rangeを使用します。

typedef std::vector< std::pair<int, int> > PairVector;

void pass(PairVector& pv) {
    auto const filter = [](std::pair<int, int> const& p) {
        return p.first % 2 != 0;
    };
    auto const transformer = [](std::pair<int, int> const& p) {
        return std::make_pair(p.first, p.second / 2);
    };

    pv.erase(
        boost::transform(pv | boost::adaptors::filtered( filter ),
                         std::back_inserter(pv),
                         transformer),
        pv.end()
    );
}

transformおよびfilteredアダプターは、他の多くのドキュメントとともに見つけることができます。

于 2012-05-24T12:58:49.833 に答える