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
(最后一个元素)。
然后进入那个outer的body 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
被它的增量部分所淘汰,要么被break
你提供的代码中的 a 所淘汰。这就是为什么x
看起来它被分配给“仅一次”但实际上它被分配给外循环的每次迭代。
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); }
};
因此,为了弥补__typeof__
(类似于decltype
C++11 的)的不足,我们必须使用多态性。该qForeachContainerNew
函数按值返回 aQForeachContainer<T>
但由于temporaries 的生命周期延长,如果我们将其存储在 a 中const QForeachContainer&
,我们可以将其生命周期延长到外部结束for
(实际上是if
因为 VC6 的错误)。我们可以将 a 存储QForeachContainer<T>
在 a 中,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;
}
这是您可能不知道发生了什么的地方,因为这些功能似乎毫无意义。以下是它们的工作方式以及您需要它们的原因:
我们将 aQForeachContainer<T>
存储在对 a 的引用中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> *>(base);
}
第二个参数总是NULL
因为我们true ? 0
总是计算0
结果T
为):QForeachContainer<T>*
for
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
并condition
返回:
(!brk++ && i != e)
这与上面的 GCC 版本相同,只是它brk
在评估后递增。所以!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
Andbrk
为 0,即false
退出循环。我们来到外部的增量部分for
:
++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()
外部循环将为 false,外部循环将为break
.
正如 GManNickG 在评论中所说,这个宏很像 Boost 的BOOST_FOREACH
,你可以在这里阅读。这样就搞定了,希望能帮到你。