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にインクリメントすると、!brkfalseになり、外側のループの条件は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&forifQForeachContainer<T>QForeachContainerBaseQForeachContainerBase&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は、条件がである場合を除いて評価されません。また、条件自体であるため、評価せずに正しいタイプを取得できます。NULLqForeachContainerqForeachContainer(&_container_, qForeachPointer(cont))cont?:falsetruecont
これでそれが解決され、適切なタイプ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 ? 00TQForeachContainer<T>*for
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
そしてcondition戻ります:
(!brk++ && i != e)
brkこれは、評価後にインクリメントすることを除いて、上記のGCCバージョンと同じです。したがって!brk++、に評価されてtrueからbrk1にインクリメントされます。
次に、内部に入り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のマクロによく似ています。だからあなたはそれを持っています、それがあなたを助けることを願っています。