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

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

问题描述

在 Qt 中,有一个使用宏 (Q_FOREACH) 实现的 foreach 循环.有不同的实现,取决于编译器.

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

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:

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

Q_FOREACH 宏中的容器必须是一个 T 类,它至少必须提供一个 T::const_iterator 类型,一个T.begin() 和一个 T.end() 方法,所有 STL 容器以及大多数 Qt 容器(如 QListQVector, QMap, QHash, ...

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, ...

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

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

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?

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

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)

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

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?

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

And why do we need two for-loops?

如果有人能让我更清楚一点就好了!

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

推荐答案

GCC 版本


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

The GCC version


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

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.然后测试条件:

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 为零,所以 !brk 为真,并且大概如果容器有任何元素 i(当前元素)没有等于 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.

然后输入那个外层for的主体,即:

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

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

所以 x 被设置为 *_container_.i 这是迭代所在的当前元素,并且没有条件所以大概这个循环将永远持续下去.然后进入循环体,这是我们的代码,它只是一个注释,所以它什么都不做.

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

它递减 brk 所以现在是 -1,并跳出循环(使用 __extension__ 这使得 GCC 不会发出使用 GCC 扩展的警告,就像你现在知道的那样).

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

它再次增加 brk 并再次使它为 0,然后 i 增加所以我们到达下一个元素.检查条件,并且由于 brk 现在是 0 并且 i 大概不等于 e (如果我们有更多元素)过程重复.

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.

为什么我们要先减少然后增加brk?原因是因为如果我们在代码体中使用了break,内循环的增量部分将不会被执行,就像这样:

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

然后brk跳出内循环时仍为0,然后进入外循环的增量部分,自增为1,然后!brk 为假,外循环的条件为假,foreach 将停止.

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.

诀窍是要意识到有两个 for 循环;外部的生命周期是整个 foreach,而内部的生命周期仅持续一个元素.它是一个无限循环,因为它没有条件,但它要么被它的增量部分break删除,要么被break 在您提供的代码中.这就是为什么 x 看起来像是被分配给了只有一次",但实际上它在外循环的每次迭代中都被分配了.

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.

VS 版本有点复杂,因为它必须解决缺少 GCC 扩展 __typeof__ 和块表达式的问题,并且它为 (6) 编写的 VS 版本没有'没有 auto 或其他花哨的 C++11 特性.

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
        }

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

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
}

其次,我们看一下外层for的初始化:

Second, let's look at the initialisation of the outer 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); }
};

所以为了弥补__typeof__(类似于C++11的decltype)的不足,我们不得不使用多态.qForeachContainerNew 函数按值返回 QForeachContainer,但由于 临时对象的生命周期延长,如果我们将它存储在一个const QForeachContainer&中,我们可以延长它的生命周期直到外部 for 的结尾(实际上是 if 因为 VC6 的错误).我们可以在QForeachContainerBase中存储一个QForeachContainer,因为前者是后者的子类,我们必须像QForeachContainerBase& 而不是像 QForeachContainerBase 这样的值以避免切片.

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.

那么对于外层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;
}

这是您可能不知道发生了什么的地方,因为这些功能似乎毫无意义.下面是它们的工作原理以及您需要它们的原因:

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:

我们有一个 QForeachContainer<T> 存储在对 QForeachContainerBase 的引用中,并且无法将其取出(我们可以看到).我们必须以某种方式将其强制转换为正确的类型,这就是两个函数的用武之地.但是我们如何知道将其强制转换为什么类型?

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?

三元运算符 x 的规则?y : zyz 必须是同一类型.我们需要知道容器的类型,所以我们使用 qForeachPointer 函数来做到这一点:

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)

qForeachPointer的返回类型是T*,所以我们使用模板类型推导来推导容器的类型.

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

真的吗?0 : qForeachPointer(cont) 是为了能够将正确类型的 NULL 指针传递给 qForeachContainer 以便它知道要转换我们的指针的类型给它.为什么我们为此使用三元运算符而不是仅仅使用 qForeachContainer(&_container_, qForeachPointer(cont))?这是为了避免多次评估 cont.?: 的第二个(实际上是第三个)操作数不会被求值,除非条件是 false,而且由于条件本身是 true,我们可以无需评估即可获得正确类型的 cont.

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.

这样就解决了,我们使用 qForeachContainer_container_ 转换为正确的类型.电话是:

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

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

再次定义是

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

第二个参数总是 NULL 因为我们做 true ?0 总是计算为 0,我们使用 qForeachPointer 来推导出 T 类型,并使用它来将第一个参数转换为 QForeachContainerT>* 所以我们可以使用它的成员函数/变量和条件(仍然在外部 for 中):

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

并且 condition 返回:

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

与上面的 GCC 版本相同,只是它在评估后增加 brk.所以 !brk++ 计算结果为 true,然后 brk 增加到 1.

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.

然后我们输入内部的for并开始初始化:

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

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

它只是将变量设置为迭代器 i 指向的内容.

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

那么条件:

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

由于brk为1,进入循环体,也就是我们的注释:

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

// stuff

然后输入增量:

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

这将 brk 递减回 0.然后再次检查条件:

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

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

并且 brk 为 0,即 false 并退出循环.我们来到外层for的增量部分:

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

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

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

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

检查 brk 是否为 0(确实如此)并再次将其递增为 1,如果 i != e 则重复该过程.

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

这在客户端代码中处理 break 与 GCC 版本仅略有不同,因为如果我们使用 break 在我们的代码,它仍然是 1,并且 condition() 对于外循环将是 false,外循环将 break.

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.

正如 GManNickG 在评论中所说,这个宏很像 Boost 的 BOOST_FOREACH,你可以阅读它关于 这里.就这样吧,希望能帮到你.

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天全站免登陆