gpt4 book ai didi

c++ - Q_FOREACH (= foreach) 宏是如何工作的,为什么这么复杂?

转载 作者:行者123 更新时间:2023-12-02 08:33:15 24 4
gpt4 key购买 nike

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

定义海湾合作委员会 如下:

#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 容器(如 QList)也是如此。 , QVector , QMap , QHash , ...

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

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

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

#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 循环?

如果有人能让我更清楚一点,那就太酷了!

最佳答案

海湾合作委员会版本

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 (最后一个元素)呢。

然后是 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 退出在您提供的代码中。这就是为什么 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__的不足(类似于 C++11 的 decltype)我们必须使用多态。 qForeachContainerNew函数返回 QForeachContainer<T>按值(value)但由于 lifetime extension of temporaries ,如果我们将它存储在 const QForeachContainer& 中,我们可以延长它的生命周期直到外层结束 for (实际上是 if 因为 VC6 的错误)。我们可以存储一个 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 : zyz必须是同一类型。我们需要知道容器的类型,所以我们使用 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 ,我们使用 qForeachPointer 来推断类型 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

brk是 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()外循环为假,外循环为 break .

正如 GManNickG 在评论中所说,这个宏很像 Boost 的 BOOST_FOREACH您可以阅读有关 here 的信息.就这样吧,希望能帮到你。

关于c++ - Q_FOREACH (= foreach) 宏是如何工作的,为什么这么复杂?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10522155/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com