Qt foreachの注意点 その2

Qt foreachの注意点 その1 - unstable diary

Qtのforeachを使う際の注意点その2です。

Qtのforeachではc++11の範囲for文(range-based for)やboostのBOOST_FOREACHのように
受け取るコンテナの内部要素の型を参照(非const)で受けとり、コンテナの内部の値を変更するということができません。

QVector<int> vec(10);

// c++11 range-based for  ## OK
for( int &value : vec )
{
  ++value;
}

// boost BOOST_FOREACH    ## OK
BOOST_FOREACH( int &value, vec )
{
  ++value;
}

// Qt foreach             ## ERROR
foreach( int &value, vec )
{
  ++value;
}

Qtの公式のドキュメントにはこのように書いてあります。
Container Classes | Documentation | Qt Project

Qt automatically takes a copy of the container when it enters a foreach loop. If you modify the container as you are iterating, that won't affect the loop. (If you do not modify the container, the copy still takes place, but thanks to implicit sharing copying a container is very fast.)

拙訳するとこんな感じでしょうか

Qtはforeachループに入るときに自動的にコンテナのコピーを行います。そのため、もしループ内でコンテナ変更を行なってもループに影響を与えません。(コンテナを変更しない場合コピーは行われますが暗黙の共有のおかげでコピーは非常に高速です)

このようにQtのforeachではループ内でのコンテナの変更がループの結果への影響を与えないように、こういう仕様となっているようです。もしコピーが行われない場合、コンテナのサイズ変更などループ内で使用しているイテレータを無効にしてしまう変更を行った場合は未定義の動作となってしまいます。

また、ドキュメント内の暗黙の共有(implicit sharing)というのはコピーオンライト(COW:Copy On Write)とも呼ばれる最適化手法で、Qt内のコンテナを含む多くのQtクラスで採用されています。

暗黙の共有を採用したクラスではそのインスタンスをコピーしただけでは内部に持っているデータはコピー元とコピー先で共有した状態となっており、実際にどちらかのインスタンスで内部データの変更を行う際に共有を解き、実際のコピーを行います。(共有した状態でのコピーをよく浅いコピー(shallow copy)、内部データのコピーを深いコピー(deep copy)といいます)

このためQtコンテナではコピーを行なっても変更をしなければコピーコストを最小限に抑えることができるのです。

ですが暗黙の共有を採用していないコンテナクラス(STLのコンテナクラスなど)をQtのforeachで使用するときは内部データまでコピーが行われるため注意する必要があります。


参考
Container Classes | Documentation | Qt Project
c++ - QT: why is foreach iterating with a const reference? - Stack Overflow
Implicit Sharing | Documentation | Qt Project
コピーオンライト - Wikipedia
技術情報の共有化: Qtの暗黙の共有