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__(类似于decltypeC++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,你可以在这里阅读。这样就搞定了,希望能帮到你。