36

Qt には、foreachマクロ ( ) を使用して実装されるループがありますQ_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マクロ内のコンテナーは、すべての STL コンテナーや 、 、 、などのほとんどの Qt コンテナーと同様に、T少なくともT::const_iterator型、 、T.begin()およびメソッドを提供する必要があるクラスである必要があります。T.end()QListQVectorQMapQHash

私の質問は次のとおりです。このマクロはどのように機能しますか?

1 つのことは、非常に奇妙に思えます: 変数は、マクロ定義で 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ませんか?qForeachPointer(container)前の条件?が真でも関数呼び出しは実行されますか?

そして、なぜ 2 つの for ループが必要なのでしょうか?

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

4

1 に答える 1

74

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はそれyzあり、同じタイプでなければなりません。コンテナのタイプを知る必要があるので、qForeachPointer関数を使用して次のことを行います。

qForeachPointer(cont)

の戻り型qForeachPointerT*であるため、テンプレート型の推定を使用してコンテナの型を推定します。

は、正しい型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のマクロによく似ています。だからあなたはそれを持っています、それがあなたを助けることを願っています。

于 2012-05-09T18:56:04.673 に答える