GCCバージョン
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
真です。おそらく、コンテナに要素(現在の要素)がまだ(最後の要素)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つの要素に対してのみ持続します。条件がないため無限ループになりますがbreak
、インクリメント部分または指定しbreak
たコードのいずれかによって削除されます。そのためx
、「1回だけ」に割り当てられているように見えますが、実際には、外側のループのすべての反復で割り当てられています。
VSバージョン
VSバージョンは、GCC拡張機能とブロック式の欠如を回避する必要があるため、もう少し複雑です。また、(6)用に作成されたバージョンのVSには、その他の高度なC++11機能__typeof__
がありませんでした。auto
以前に使用した拡張の例を見てみましょう。
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__typeof__
のに類似した)不足を補うために、ポリモーフィズムを使用する必要があります。decltype
関数は値qForeachContainerNew
でaを返しますが、一時的なものの存続期間の延長により、それをに格納すると、アウターの終わりまでその存続期間を延長できます(実際にはVC6のバグのため)。前者は後者のサブクラスであるため、をに格納できます。スライスを避けるために、のような値ではなく、のような参照にする必要があります。QForeachContainer<T>
const QForeachContainer&
for
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;
}
これらの機能は無意味に見えるので、ここで何が起こっているのかわからないかもしれません。それらがどのように機能し、なぜそれらが必要なのかを次に示します。
QForeachContainer<T>
への参照に保存されていますが、元QForeachContainerBase
に戻す方法はありません(表示されています)。どういうわけかそれを適切な型にキャストする必要があり、そこで2つの関数が登場します。しかし、どの型にキャストするかをどうやって知るのでしょうか。
三項演算子の規則x ? y : z
はそれy
でz
あり、同じタイプでなければなりません。コンテナのタイプを知る必要があるので、qForeachPointer
関数を使用して次のことを行います。
qForeachPointer(cont)
の戻り型qForeachPointer
はT*
であるため、テンプレート型の推定を使用してコンテナの型を推定します。
は、正しい型true ? 0 : qForeachPointer(cont)
のポインタを渡すことができるようにすることです。これにより、ポインタをどの型にキャストするかがわかります。なぜこれを行うのではなく、三項演算子を使用するのですか?何度も評価することを避けるためです。2番目(実際には3番目)のオペランドtoは、条件がである場合を除いて評価されません。また、条件自体であるため、評価せずに正しいタイプを取得できます。NULL
qForeachContainer
qForeachContainer(&_container_, qForeachPointer(cont))
cont
?:
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
ためです。qForeachPointerを使用して型を推定し、それを使用して最初の引数をaにキャストし、そのメンバー関数/変数を条件付きで使用できるようにします(まだ外部にあります)。)::true ? 0
0
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
は1なのでbrk
、ループの本体が入力されます。これがコメントです。
// 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()
これは、それが0(それがそうである)をチェックし、brk
それを再び1にインクリメントし、。の場合、プロセスが繰り返されますi != e
。
break
これは、GCCバージョンとは少し異なる方法でクライアントコードを処理します。これは、コードbrk
で使用してもデクリメントされないためbreak
、1のままでありcondition()
、外側のループと外側のループはfalseになりますbreak
。
そして、GManNickGがコメントで述べたように、このマクロは、ここでBOOST_FOREACH
読むことができるBoostのマクロによく似ています。だからあなたはそれを持っています、それがあなたを助けることを願っています。