Qtには、マクロ(_Q_FOREACH
_)を使用して実装されるforeach
ループがあります。コンパイラに応じて、さまざまな実装があります。
定義GCCの場合は次のとおりです。
_#define Q_FOREACH(variable, container) \
for (QForeachContainer<__typeof__(container)> _container_(container); \
!_container_.brk && _container_.i != _container_.e; \
__extension__ ({ ++_container_.brk; ++_container_.i; })) \
for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;}))
_
...次のように定義されているヘルパークラスQForeachContainer
を使用します。
_template <typename T>
class QForeachContainer {
public:
inline QForeachContainer(const T& t) : c(t), brk(0), i(c.begin()), e(c.end()) { }
const T c;
int brk;
typename T::const_iterator i, e;
};
_
_Q_FOREACH
_マクロのコンテナは、クラスT
である必要があります。これは、少なくとも_T::const_iterator
_タイプ、T.begin()
およびT.end()
メソッドを提供する必要があります。すべてのSTLコンテナと、QList
、QVector
、QMap
、QHash
などのほとんどのQtコンテナを実行します。
私の質問は今です:このマクロはどのように機能しますか?
奇妙なことの1つは、変数がマクロ定義に一度だけ現れることです。したがって、たとえばforeach(QString item, list)
には_QString item =
_がありますが、その後_item =
_はありません...変数item
を各ステップでどのように変更できますか?
さらに混乱するのは、次の_Q_FOREACH
_の定義ですMS VC++コンパイラの場合:
_#define Q_FOREACH(variable,container) \
if(0){}else \
for (const QForeachContainerBase &_container_ = qForeachContainerNew(container); \
qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->condition(); \
++qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->i) \
for (variable = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->i; \
qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->brk; \
--qForeachContainer(&_container_, true ? 0 : qForeachPointer(container))->brk)
_
なぜ_true : 0 ? ...
_なのですか?これは常に_0
_に評価されませんか? _?
_の前の条件がtrueであっても、関数呼び出しqForeachPointer(container)
は実行されますか?
そして、なぜ2つのforループが必要なのでしょうか。
誰かが私のために物事を少し明確にすることができればそれはクールです!
GCCは非常に単純です。まず、次のように使用します。
_Q_FOREACH(x, cont)
{
// do stuff
}
_
そして、それはに拡大されます
_for (QForeachContainer<__typeof__(cont)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; }))
for (x = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
// do stuff
}
_
まず最初に:
_for (QForeachContainer<__typeof__(cont)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; }))
_
これは実際のfor
ループです。反復を支援するQForeachContainer
を設定します。 brk
変数は0に初期化されます。次に、条件がテストされます。
_!_container_.brk && _container_.i != _container_.e
_
brk
はゼロなので、_!brk
_はtrueであり、おそらくコンテナに要素i
(現在の要素)がe
(最後の要素)とまだ一致していない可能性があります。
次に、その外側のfor
の本体が入力されます。
_for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
// do stuff
}
_
したがって、x
は、反復が行われている現在の要素である_*_container_.i
_に設定され、条件がないため、おそらくこのループは永久に継続します。次に、ループの本体(コード)に入ります。これは単なるコメントなので、何もしません。
次に、内部ループの増分部分に入ります。これは興味深いものです。
___extension__ ({--_container_.brk; break;})
_
これはbrk
をデクリメントして-1になり、ループから抜け出します(___extension__
_を使用すると、GCCがGCC拡張機能の使用に関する警告を発行しなくなります)。
次に、外側のループの増分部分に入ります。
___extension__ ({ ++_container_.brk; ++_container_.i; })
_
これにより、brk
が再びインクリメントされて再び0になり、次にi
がインクリメントされて次の要素に移動します。条件がチェックされ、brk
が0になり、i
がおそらくe
とまだ一致していないため(さらに要素がある場合)、プロセスが繰り返されます。
なぜそのようにbrk
をデクリメントしてからインクリメントしたのですか?その理由は、次のようにコードの本体でbreak
を使用した場合、内部ループのインクリメント部分が実行されないためです。
_Q_FOREACH(x, cont)
{
break;
}
_
次に、brk
は、内側のループから抜け出すときも0のままで、外側のループのインクリメント部分に入り、それを1にインクリメントし、次に_!brk
_がfalseになり、外側のループの条件が次のように評価されます。 false、foreachは停止します。
コツは、2つのfor
ループがあることを理解することです。外側のライフタイムはforeach全体ですが、内側のライフタイムは1つの要素に対してのみ持続します。条件がないため、これはwould無限ループになりますが、インクリメントパーツまたはbreak
によってbreak
edされますあなたが提供するコードそのため、x
は「1回だけ」に割り当てられているように見えますが、実際には、外側のループのすべての反復で割り当てられます。
VSバージョンは、GCC拡張___typeof__
_とブロック式の欠如を回避する必要があるため、もう少し複雑であり、それが(6)のために作成されたVSのバージョンには、auto
またはその他のファンシーがありませんでしたC++ 11の機能。
前に使用した拡張の例を見てみましょう。
_if(0){}else
for (const QForeachContainerBase &_container_ = qForeachContainerNew(cont); qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition(); ++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i)
for (x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i; qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk; --qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk)
{
// stuff
}
_
if(0){}else
は、VC++ 6がfor
変数のスコープを誤っており、for
ループの初期化部分で宣言された変数がループの外で使用される可能性があるためです。したがって、これはVSバグの回避策です。ただif(0){}else
ではなくif(0){...}
を実行した理由は、次のように、ループの後にelse
を追加できないようにするためです。
_Q_FOREACH(x, cont)
{
// do stuff
} else {
// This code is never called
}
_
次に、外側のfor
の初期化を見てみましょう。
_const QForeachContainerBase &_container_ = qForeachContainerNew(cont)
_
QForeachContainerBase
の定義は次のとおりです。
_struct QForeachContainerBase {};
_
そしてqForeachContainerNew
の定義は
_template <typename T>
inline QForeachContainer<T>
qForeachContainerNew(const T& t) {
return QForeachContainer<T>(t);
}
_
そしてQForeachContainer
の定義は
_template <typename T>
class QForeachContainer : public QForeachContainerBase {
public:
inline QForeachContainer(const T& t): c(t), brk(0), i(c.begin()), e(c.end()){};
const T c;
mutable int brk;
mutable typename T::const_iterator i, e;
inline bool condition() const { return (!brk++ && i != e); }
};
_
したがって、C++ 11のdecltype
に類似した___typeof__
_の欠如を補うために、ポリモーフィズムを使用する必要があります。 qForeachContainerNew
関数は値によって_QForeachContainer<T>
_を返しますが、 temporariesの寿命延長 のため、これを_const QForeachContainer&
_に保存すると、外側の終わりまで寿命を延ばすことができますfor
(VC6のバグのため、実際にはif
)。前者は後者のサブクラスであるため、_QForeachContainer<T>
_をQForeachContainerBase
に格納できます。スライスを回避するには、QForeachContainerBase
のような値ではなく、_QForeachContainerBase&
_のような参照にする必要があります。
次に、外側のfor
の条件について:
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition();
_
qForeachContainer
の定義は
_inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
_
そしてqForeachPointer
の定義は
_template <typename T>
inline T *qForeachPointer(const T &) {
return 0;
}
_
これらの関数は一種の意味がないように見えるので、これは何が起こっているのかを知らないかもしれません。よくここにそれらがどのように機能するか、なぜあなたがそれらを必要とするかです:
QForeachContainerBase
への参照に格納されている_QForeachContainer<T>
_があり、それを元に戻す方法はありません(表示されています)。どういうわけかそれを適切な型にキャストする必要があり、そこに2つの関数が含まれています。しかし、どの型にキャストするかをどうやって知るのでしょうか。
三項演算子_x ? y : z
_の規則では、y
とz
は同じ型でなければなりません。コンテナーのタイプを知る必要があるので、qForeachPointer
関数を使用してそれを行います。
_qForeachPointer(cont)
_
qForeachPointer
の戻り値の型は_T*
_であるため、テンプレート型の推定を使用してコンテナーの型を推定します。
true ? 0 : qForeachPointer(cont)
は、適切な型のNULL
ポインターをqForeachContainer
に渡すことができるようにするため、与えられたポインターをキャストする型を認識します。 qForeachContainer(&_container_, qForeachPointer(cont))
を実行するのではなく、なぜこれに三項演算子を使用するのですか? cont
を何度も評価しないようにするためです。 _?:
_の2番目(実際には3番目)のオペランドは、条件がfalse
でない限り評価されません。条件がtrue
自体なので、評価せずにcont
の正しい型を取得できます。
これで解決し、qForeachContainer
を使用して__container_
_を適切な型にキャストします。呼び出しは次のとおりです。
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))
_
そして再び、定義は
_inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
_
2番目のパラメーターは常にNULL
になります。これは、常に_true ? 0
_に評価される_0
_を実行し、qForeachPointerを使用してT
型を推定し、それを使用して最初の引数を_QForeachContainer<T>*
_にキャストするためですそのため、メンバー関数/変数を条件付きで使用できます(まだfor
の外側にあります)。
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
_
また、condition
は次を返します:
_(!brk++ && i != e)
_
これは、評価後にbrk
をインクリメントすることを除いて、上記のGCCバージョンと同じです。したがって、_!brk++
_はtrue
と評価され、次にbrk
が1にインクリメントされます。
次に、内部のfor
を入力し、初期化から始めます。
_x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
_
これは、変数をイテレータi
が指しているものに設定するだけです。
次に条件:
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
_
brk
は1なので、ループの本体に入ります。これがコメントです。
_// stuff
_
次に、増分が入力されます。
_--qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
_
これにより、brk
が0にデクリメントされます。次に、条件が再度チェックされます。
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
_
そして、brk
は0で、これはfalse
であり、ループは終了します。外側のfor
の増分部分に到達します。
_++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
_
そして、それはi
を次の要素にインクリメントします。次に、条件を取得します。
_qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
_
これは、brk
が0(ゼロ)であることを確認し、再び1にインクリメントします。_i != e
_の場合、プロセスが繰り返されます。
break
は、コードでbrk
を使用しても減分されず、1のままで、condition()
が外部コードに対してfalseになるため、クライアントコードのbreak
はGCCバージョンと少し異なるだけで処理されます。ループと外側のループはbreak
になります。
そして、GManNickGがコメントで述べたように、このマクロは here について読むことができるBoostの_BOOST_FOREACH
_によく似ています。それであなたはそれを手に入れました、それがあなたを助けることを願っています。