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の暗黙の共有

Qt QGraphicsItemごとに個別にアンチエイリアスをかける

QGraphiceViewではsetRenderHint関数でQPainter::Antialiasingを設定してやることで
以下のようにアンチエイリアスのオン オフを切り替えられます。

アンチエイリアス オン
f:id:nukesaq88:20130321191848p:plain

アンチエイリアス オフ
f:id:nukesaq88:20130321191845p:plain

このようにアンチエイリアスの設定を切り替えられますが、
この設定はQGraphicsView全体に適用されてしまいます。

もしQGraphicsItemごとに個別にアンチエイリアスをかけたい場合は
QGraphicsItemを継承したclassでpaint関数を以下のようにオーバーライドすることで
個別のItemにアンチエイリアスをかけることができます。
例はQGraphicsEllipseItemを継承した場合です。

virtual void MyEllipseItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
{
  painter->setRenderHints( painter->renderHints() | QPainter::Antialiasing );
  QGraphicsEllipseItem::paint( painter, option, widget );
}

結果: 赤色のItemだけアンチエイリアス オン
f:id:nukesaq88:20130321191855p:plain


参考
QGraphicsView: per-item antialiasing specification?

Qt foreachの注意点 その1

c++11で範囲for文(range-based for)が追加されたことで、c++でも以下のようにコンテナの内部のすべての要素を簡単に捜査できるようになりました。

std:vector<int> vec;
for( const int value : vec )
{
  std::cout << value << std::endl;
}

Qtではc++の言語機能としてこの機能が追加される以前からQt独自の拡張としてforeach機能を提供しており、以下のようにコンテナの要素を簡単に捜査することができました。

QVector<int> vec;
foreach( const int value, vec )
{
  qDebug() << value;
}

このようにc++11が使えない環境では大変便利なforeach機能ですが1つ注意点があり、
以下の例のようにコンテナの内部要素の型にカンマが入っているとコンパイルエラーとなります。

QVector< QPair<int,int> > vec;
foreach( const QPair<int,int> &value, vec )
{
  qDebug() << value.first << value.second;
}

clangだとこのようなエラー内容

error: too many arguments provided to function-like macro invocation

これはQtのforeach機能がマクロで実装されているため、型のカンマが引数の区切りとして以下のように解釈されてしまうことでこのようなエラーが発生します。

foreach( (const QPair<int), (int> &value), (vec) )

このエラーはコンテナの内部要素の型をtypedefしてやることで回避出来ます。

typedef QPair<int,int> Pair_t;
QVector<Pair_t> vec;
foreach( const Pair_t value, vec )
{
  qDebug() << value.first << value.second;
}


知らないときにエラーになって少しはまったことがあるのでメモ。


ちなみにboostのBOOST_FOREACHでも同じ理由から同様のエラーが起きるようです。

参考
Container Classes | Documentation | Qt Project
C++でforeachみたいなことができるBoost.Foreach - ぬいぐるみライフ(仮)

pythonでfizzbuzzワンライナー

なんとなくpythonfizzbuzzワンライナーをやってみた。


とりあえず何も考えずに条件演算式(c++とかの三項演算子みたいなもの)を使って作成したワンライナー

print("\n".join(["fizzbuzz" if (x % 3) == 0 and (x % 5) == 0 else "fizz" if (x % 3) == 0 else "buzz" if (x % 5) == 0 else str(x) for x in range(1, 101)]))


一応期待どおり動いたが、長いので少しだけ改良。

print("\n".join([((""if x%3 else"fizz")+(""if x%5 else"buzz"))or str(x) for x in range(1,101)]))

この改良版ではpythonの条件判定では、空の文字列は偽(false)と評価されることと
or演算子では返す値を 0 や 1 に制限するのではなく、最後に評価した引数の値を返すことを利用して少しだけ短縮した。
式 (expression) — Python 2.7ja1 documentation


更に短縮してみたいが今のところパッと思いつかないので、思いついた時に書くことにしよう。