Q_FOREACH(= foreach)宏是如何工作的,为什么它是复杂的? [英] How does Q_FOREACH (= foreach) macro work and why is it that complex?

查看:1714
本文介绍了Q_FOREACH(= foreach)宏是如何工作的,为什么它是复杂的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在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;}))


b $ b

...使用辅助类 QForeachContainer ,其定义如下:

  template< typename T> 
class QForeachContainer {
public:
inline QForeachContainer(const T& t):c(t),brk(0),i(c.begin()),e ()){}
const T c;
int brk;
typename T :: const_iterator i,e;
};

Q_FOREACH 宏中的容器必须是至少必须提供 T :: const_iterator 类型 T 所有STL容器以及大多数Qt容器,如$ T.end() c> QList QVector code>,...



我的问题现在是:此宏如何工作?



有一件事似乎很奇怪:变量在宏定义中只出现一次。例如 foreach(QString item,list)有一个 QString item = ,但没有 item = code>随后在任何时候...如何在每个步骤中更改变量 item



更令人困惑的是对于MS VC ++编译器的 Q_FOREACH 的以下定义:



<如果(const QForeachContainerBase& amp; amp; amp; C),则返回一个错误信息。 ; _container_ = qForeachContainerNew(container); \
qForeachContainer(& _container_,true?0:qForeachPointer(container)) - > condition(); \
++ qForeachContainer(& _container_, true> 0:qForeachPointer(container)) - > i)\
for(variable = * qForeachContainer(& _container_,true?0:qForeachPointer \
qForeachContainer(& _container_,true?0:qForeachPointer(container)) - > brk; \
--qForeachContainer(& #container_,true?0:qForeachPointer(container)) - > brk)

为什么 true:0? ... ?这不总是得到 0 评估?是否即使之前的条件为真,函数调用 qForeachPointer(container)吗?



为什么我们需要两个for循环?



如果任何人可以让事情更清楚我会很酷! / p>

解决方案

GCC版本





$ b b

GCC真的很简单。首先它是这样使用的:

  Q_FOREACH(x,cont)
{
// do stuff
}

这将会扩展到

  for(QForeachContainer< __ typeof __(container)> _container_(cont);!_container_.brk& _ amptain-container_.i!= _container_.e; __extension__ {$ _ container_.brk; ++ _ container_.i;}))
for(x = * _container_.i ;; __extension__({--_ container_.brk; break;}))
{
// do stuff
}

首先:

  for(QForeachContainer< __ typeof __(container)> _container_(container);!_container_.brk& _container_.i!= _container_。 e; __extension__({++ _ container_.brk; ++ _ container_.i;}))

是 c> c>的实际。它设置一个 QForeachContainer 以帮助迭代。 brk 变量初始化为0.然后测试条件:

 !_ container_.brk&&& _container_.i!= _container_.e 

brk 是零,所以!brk 为true,并且假设容器有任何元素 i (当前元素)

然后,外部的主体 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 可能不等于



为什么我们递减,然后增加 brk 这样吗?原因是因为如果我们在代码主体中使用 break ,内循环的增量部分将不会被执行,如下所示:

  Q_FOREACH(x,cont)
{
break;
}

然后 brk 当它跳出内循环时仍然为0,然后将进入外循环的增量部分并将其增加到1,则!brk 将为false,外部循环的条件将计算为false,foreach将停止。



诀窍是意识到有两个 for loops;外部的一生是整个foreach,但内部的一个只持续一个元素。它是一个无限循环,因为它没有条件,但它是 break ed由它的增量部分,或者在您提供的代码中添加 break 。这就是为什么 x 看起来像是分配给只有一次,但实际上是分配给外循环的每次迭代。



VS版本






VS版本有点复杂,因为它必须的GCC扩展 __ typeof __ 和块表达式,并且它为(6)写的VS的版本没有 auto 或其他奇特的C ++ 11功能。



让我们看看我们之前使用的扩展示例:


$ b $对于(const QForeachContainerBase& _container_ = qForeachContainerNew(cont); qForeachContainer(& _container_,true?0:qForeachPointer(cont)); b

  )) - > condition(); ++ qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > i)
for(x = * qForeachContainer(& _container_,true? qForeachPointer(cont)) - > i; qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > brk; --qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > brk)
{
// stuff
}

if(0){} else 是因为VC ++ 6的范围界定 for 变量错误,并且在循环的初始化部分中声明的变量可以在循环外部使用。所以这是一个VS bug的解决方法。原因他们 if(0){} else 而不是只是 if(0){...} 因此您不能在循环后添加 else ,例如

  Q_FOREACH(x,cont)
{
// do stuff
} else {
//这段代码从不调用
}

其次,让我们来看看对于的初始化:

  const QForeachContainerBase& _container_ = qForeachContainerNew(container)

QForeachContainerBase 的定义是:

  struct QForeachContainerBase {}; 

qForeachContainerNew 的定义是



  template< typename T> 
inline QForeachContainer< T>
qForeachContainerNew(const T& t){
return QForeachContainer< T>(t);
}

QForeachContainer

 模板< typename T& 
class QForeachContainer:public QForeachContainerBase {
public:
inline QForeachContainer(const T& t):c(t),brk(0),i(c.begin c.end()){};
const T c;
mutable int brk;
mutable typename T :: const_iterator i,e;
inline bool condition()const {return(!brk ++&& i!= e); }
};

为了弥补缺少的 __ typeof __ (类似于C ++ 11的 decltype ),我们必须使用多态。 qForeachContainerNew 函数通过值返回 QForeachContainer< T> ,但是由于临时表的生命期延长,如果我们将它存储在 const QForeachContainer& ,我们可以延长它的生命周期直到外部结束(实际上 if 因为VC6的bug)。我们可以在 QForeachContainerBase 中存储一个 QForeachContainer< T> ,因为前者是后者的子类,以使其成为 QForeachContainerBase& 的引用,而不是像 QForeachContainerBase 的值以避免切片。



然后对于 for 的条件:

 code> qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > condition(); 

qForeachContainer 的定义是

  inline const QForeachContainer< T> * qForeachContainer(const QForeachContainerBase * base,const T *){
return static_cast< const QForeachContainer< T> *(基峰)。
}

而且 qForeachPointer

 模板< typename T& 
inline T * qForeachPointer(const T&){
return 0;
}

这是你可能不知道发生了什么,因为这些功能似乎有点无意义。以下是它们的工作方式以及为什么需要它们:



我们有一个 QForeachContainer< T> 到一个 QForeachContainerBase ,没有办法让它回来(我们可以看到)。我们必须以某种方式将其转换为正确的类型,这就是两个函数的来源,但是我们如何知道将它转换成什么类型​​呢?



三元运算符 x? y:z y z 必须是相同类型。我们需要知道容器的类型,所以我们使用 qForeachPointer 函数:

  qForeachPointer(cont)

qForeachPointer T * ,因此我们使用模板类型推导来推导容器的类型。



true? 0:qForeachPointer(cont)是能够将正确类型的 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> *(基峰)。
}

第二个参数总是 NULL 因为我们 true? 0 ,我们使用qForeachPointer推导类型 T 并且使用它将第一个参数转换为 QForeachContainer< T> * ,因此我们可以使用其成员函数/变量与条件(仍然在外部 for ):

  qForeachContainer(& _container_,true?0:qForeachPointer - > condition()

condition 返回:

 (!brk ++&& i!= e)
/ pre>

它与上面的GCC版本相同,除了它在评估之后增加 brk 。因此!brk ++ 计算为 true ,然后 brk 到1。



然后我们输入,并从初始化开始:

  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 

code>为0,即 false ,并退出循环。我们来到的外部增量部分:

  ++ qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > i 

i 增加到下一个元素。然后我们得到条件:

  qForeachContainer(& _container_,true?0:qForeachPointer(cont)) - > condition ()

这会检查 brk 是否为0 (它是)并再次增加到1,如果 i!= e ,则重复该过程。



这会处理客户端代码中的 break ,与GCC版本稍有不同,因为 brk 在我们的代码中使用 break ,它仍然是1,而 condition()和外循环将 break



正如GManNickG在评论中所说的,这个宏是很像Boost的 BOOST_FOREACH ,您可以在这里阅读。所以你有它,希望能帮助你。


In Qt, there is a foreach loop which is implemented using macros (Q_FOREACH). There are different implementations, depending on the compiler.

The definition for GCC is as follows:

#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;}))

... using the helper class QForeachContainer which is defined as follows:

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;
};

The container in a Q_FOREACH macro has to be a class T which at least has to provide a T::const_iterator type, a T.begin() and a T.end() method, as do all STL containers as well as most Qt containers like QList, QVector, QMap, QHash, ...

My question is now: How does this macro work?

One thing seems to be really odd: The variable only appears once in the macro definition. So e.g. foreach(QString item, list) has a QString item = but no item = afterwards at any time... How can the variable item then be changed in each step?

Even more confusing is the following definition of Q_FOREACH for the MS VC++ compiler:

#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)

Why true : 0 ? ...? Doesn't this always get evaluated to 0? Is the function call qForeachPointer(container) executed even if the condition before ? is true?

And why do we need two for-loops?

It would be cool if anyone can make things a bit clearer for me!

解决方案

The GCC version


The GCC one is really quite simple. First of all it is used like this:

Q_FOREACH(x, cont)
{
    // do stuff
}

And that will be expanded to

for (QForeachContainer<__typeof__(container)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__  ({ ++_container_.brk; ++_container_.i; }))
    for (x = *_container_.i;; __extension__ ({--_container_.brk; break;}))
    {
        // do stuff
    }

So first of all:

for (QForeachContainer<__typeof__(container)> _container_(container); !_container_.brk && _container_.i != _container_.e; __extension__  ({ ++_container_.brk; ++_container_.i; }))

This is the actual for loop. It sets up a QForeachContainer to help with the iteration. The brk variable is intitialised to 0. Then the condition is tested:

!_container_.brk && _container_.i != _container_.e

brk is zero so !brk is true, and presumably if the container has any elements i (the current element) doesn't equal e (the last element) yet.

Then the body of that outer for is entered, which is:

for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
    // do stuff
}

So x is set to *_container_.i which is the current element the iteration is on, and there is no condition so presumably this loop will continue forever. Then the body of the loop is entered, which is our code, and it's just a comment so it doesn't do anything.

Then the increment part of the inner loop is entered, which is interesting:

__extension__ ({--_container_.brk; break;})

It decrements brk so that's now -1, and breaks out of the loop (with __extension__ which makes GCC not emit warnings for using GCC extensions, like you now know).

Then the increment part of the outer loop is entered:

__extension__  ({ ++_container_.brk; ++_container_.i; })

which increments brk again and makes it 0 again, and then i is incremented so we get to the next element. The condition is checked, and since brk is now 0 and i presumably doesn't equal e yet (if we have more elements) the process is repeated.

Why did we decrement and then increment brk like that? The reason is because the increment part of the inner loop will not be executed if we used break in the body of our code, like this:

Q_FOREACH(x, cont)
{
    break;
}

Then brk would still be 0 when it breaks out of the inner loop, and then the increment part of the outer loop would be entered and increment it to 1, then !brk would be false and the outer loop's condition would evaluate to false, and the foreach would stop.

The trick is to realise that there are two for loops; the outer one's lifetime is the whole foreach, but the inner one only lasts for one element. It would be an infinite loop since it doesn't have a condition, but it is either breaked out of by it's increment part, or by a break in the code you provide it. That's why x looks like it is assigned to "only once" but actually it's assigned to on every iteration of the outer loop.

The VS version


The VS version is a little more complicated because it has to work around the lack of the GCC extension __typeof__ and block-expressions, and the version of VS it was written for (6) didn't have auto or other fancy C++11 features.

Let's look at an example expansion for what we used earlier:

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
        }

The if(0){}else is because VC++ 6 did the scoping of for variables wrong and a variable declared in the initialisation part of a for loop could be used outside the loop. So it's a workaround for a VS bug. The reason they did if(0){}else instead of just if(0){...} is so that you can't add an else after the loop, like

Q_FOREACH(x, cont)
{
    // do stuff
} else {
    // This code is never called
}

Second, let's look at the initialisation of the outer for:

const QForeachContainerBase &_container_ = qForeachContainerNew(container)

The definition of QForeachContainerBase is:

struct QForeachContainerBase {};

And the definition of qForeachContainerNew is

template <typename T>
inline QForeachContainer<T>
qForeachContainerNew(const T& t) {
    return QForeachContainer<T>(t);
}

And the definition of QForeachContainer is

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); }
};

So to make up for the lack of __typeof__ (which analogous to the decltype of C++11) we have to use polymorphism. The qForeachContainerNew function returns a QForeachContainer<T> by value but due to lifetime extension of temporaries, if we store it in a const QForeachContainer&, we can prolong it's lifetime till the end of the outer for (actually the if because of VC6's bug). We can store a QForeachContainer<T> in a QForeachContainerBase because the former is a subclass of the latter, and we have to make it a reference like QForeachContainerBase& instead of a value like QForeachContainerBase to avoid slicing.

Then for the condition of the outer for:

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition(); 

The definition of qForeachContainer is

inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
    return static_cast<const QForeachContainer<T> *>(base);
}

And the definition of qForeachPointer is

template <typename T>
inline T *qForeachPointer(const T &) {
    return 0;
}

This is where you might not be aware of what's going on since these functions seem kind of pointless. Well here's how they work and why you need them:

We have a QForeachContainer<T> stored in a reference to a QForeachContainerBase with no way to get it back out (that we can see). We have to cast it to the proper type somehow, and that's where the two functions come in. But how do we know what type to cast it to?

A rule of the ternary operator x ? y : z is that y and z must be of the same type. We need to know the type of the container, so we use the qForeachPointer function to do that:

qForeachPointer(cont)

The return type of qForeachPointer is T*, so we use template type deduction to deduce the type of the container.

The true ? 0 : qForeachPointer(cont) is to be able to pass a NULL pointer of the right type to qForeachContainer so it will know what type to cast the pointer we give it to. Why do we use the ternary operator for this instead of just doing qForeachContainer(&_container_, qForeachPointer(cont))? It's to avoid evaluating cont many times. The second (actually third) operand to ?: is not evaluated unless the condition is false, and since the condition is true itself, we can get the right type of cont without evaluating it.

So that solves that, and we use qForeachContainer to cast _container_ to the right type. The call is:

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))

And again the definition is

inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
    return static_cast<const QForeachContainer<T> *>(base);
}

The second parameter will always be NULL because we do true ? 0 which always evaluates to 0, and we use qForeachPointer to deduce the type T, and use that to cast the first argument to a QForeachContainer<T>* so we can use its member functions/variables with the condition (still in the outer for):

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()

And condition returns:

(!brk++ && i != e)

which is the same as the GCC version above except that it increments brk after evaluating it. So !brk++ evaluates to true and then brk is incremented to 1.

Then we enter the inner for and begin with the initialisation:

x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i

Which just sets the variable to what the iterator i is pointing to.

Then the condition:

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk

Since brk is 1, the body of the loop is entered, which is our comment:

// stuff

Then the increment is entered:

--qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk

That decrements brk back to 0. Then the condition is checked again:

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk

And brk is 0 which is false and the loop is exited. We come to the increment part of the outer for:

++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i

And that increments i to the next element. Then we get to the condition:

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()

Which checks that brk is 0 (which it is) and increments it to 1 again, and the process is repeated if i != e.

This handles break in client code only a little differently than the GCC version, since brk will not be decremented if we use break in our code and it will still be 1, and the condition() will be false for the outer loop and the outer loop will break.

And as GManNickG stated in the comments, this macro is a lot like Boost's BOOST_FOREACH which you can read about here. So there you have it, hope that helps you out.

这篇关于Q_FOREACH(= foreach)宏是如何工作的,为什么它是复杂的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆