Q_FOREACH(= foreach)宏如何工作以及为什么这么复杂?

时间:2012-05-09 18:41:22

标签: c++ qt macros 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;}))

...使用辅助类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容器以及大多数Qt容器一样,例如QListQVectorQMapQHash,......

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

有一点似乎很奇怪:变量只在宏定义中出现一次。所以例如之后foreach(QString item, list)有一个QString item =但之后没有item = ......如何在每一步中更改变量item

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

#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)之前的条件为真,函数调用?也会执行吗?

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

如果有人能让我的事情更清楚,那将会很酷!

1 个答案:

答案 0 :(得分:72)

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,然后!brk将为假,外环的条件会评估为假,而foreach会停止。

诀窍是要意识到有两个for循环;外在的一生是整个foreach,但内在的只持续一个元素。它是一个无限循环,因为它没有条件,但它是break由它的增量部分或{{{}}引出的。 1}}在您提供的代码中。这就是为什么break看起来被分配给&#34;只有一次&#34;但实际上它是在外循环的每次迭代中分配的。

VS版


VS版本稍微复杂一点,因为它必须解决缺少GCC扩展x和块表达式的问题,并且为(6)编写的VS版本没有做到这一点拥有__typeof__或其他奇特的C ++ 11功能。

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

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 } 是因为VC ++ 6错误地确定了if(0){}else变量的范围,并且在for循环的初始化部分中声明的变量可以在循环外使用。所以它是VS错误的解决方法。他们for而非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,但由于lifetime extension of temporaries,如果我们将其存储在QForeachContainer<T>中,我们可以延长它的生命周期直到结束外部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; } 的引用中存储了QForeachContainer<T>,无法将其取回(我们可以看到)。我们必须以某种方式将它转换为正确的类型,以及这两个函数的来源。但我们如何知道将它投射到什么类型?

三元运算符QForeachContainerBase的规则是x ? y : zy必须属于同一类型。我们需要知道容器的类型,因此我们使用z函数来执行此操作:

qForeachPointer

qForeachPointer(cont) 的返回类型为qForeachPointer,因此我们使用模板类型推导来推断容器的类型。

T*能够将正确类型的true ? 0 : qForeachPointer(cont)指针传递给NULL,这样它就会知道我们给它指针的类型。为什么我们使用三元运算符而不只是qForeachContainer?要避免多次评估qForeachContainer(&_container_, qForeachPointer(cont))。除非条件为cont,否则不会评估?:的第二个(实际是第三个)操作数,并且由于条件为false本身,我们可以获得正确的true类型没有评估它。

这样就解决了这个问题,我们使用contqForeachContainer转换为正确的类型。电话是:

_container_

同样的定义是

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,我们使用qForeachPointer来推断类型0,并使用它来将第一个参数转换为T,以便我们可以将其成员函数/变量与条件一起使用(仍在外部QForeachContainer<T>*中):

for

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

condition

与上面的GCC版本相同,只是在评估后增加(!brk++ && i != e) 。因此brk评估为!brk++,然后true增加为1.

然后我们进入内部brk并从初始化开始:

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 减少回0.然后再次检查条件:

brk

qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk 为0,即brk,退出循环。我们来到外false的增量部分:

for

并将++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i 递增到下一个元素。然后我们达到了条件:

i

哪个检查qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition() 为0(它是)并再次将其递增为1,如果brk,则重复该过程。

这在客户端代码中处理i != e的方式与GCC版本略有不同,因为如果我们在代码中使用break并且它仍然是1,brk将不会递减外部循环的break为false,外部循环为condition()

正如GManNickG在评论中所述,这个宏很像Boost的break,你可以阅读here。所以你有它,希望能帮到你。