web-dev-qa-db-ja.com

Q_FOREACH(= foreach)マクロはどのように機能し、なぜ複雑なのですか?

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コンテナと、QListQVectorQMapQHashなどのほとんどの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ループが必要なのでしょうか。

誰かが私のために物事を少し明確にすることができればそれはクールです!

35
leemes

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_は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によってbreakedされますあなたが提供するコードそのため、xは「1回だけ」に割り当てられているように見えますが、実際には、外側のループのすべての反復で割り当てられます。

VSバージョン


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_の規則では、yzは同じ型でなければなりません。コンテナーのタイプを知る必要があるので、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_によく似ています。それであなたはそれを手に入れました、それがあなたを助けることを願っています。

74
Seth Carnegie