Comment la macro FOREACH (= foreach) Q fonctionne-t-elle et pourquoi est-elle si complexe?
dans Qt, il y a une boucle foreach
qui est implémentée en utilisant des macros ( Q_FOREACH
). Il existe différentes implémentations, selon le compilateur.
la définition de pour GCC est la suivante:
#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;}))
... en utilisant la classe helper QForeachContainer
qui est défini comme suit:
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;
};
le conteneur dans une macro Q_FOREACH
doit être une classe T
qui au moins doit fournir un type T::const_iterator
, un T.begin()
et une méthode T.end()
, comme le font tous les conteneurs STL ainsi que la plupart des conteneurs QT comme QList
, QVector
, QMap
, QHash
, ...
Ma question est maintenant: Comment cette macro?
une chose semble vraiment étrange: la variable n'apparaît qu'une seule fois dans la définition macro. Ainsi par exemple foreach(QString item, list)
a un QString item =
mais aucun item =
après à tout moment... Comment la variable item
peut-elle alors être changée à chaque étape?
encore plus confuse est la définition suivante de Q_FOREACH
pour le compilateur 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)
pourquoi true : 0 ? ...
? Ça n'est pas toujours évalué en 0
? Est-ce que l'appel de fonction qForeachPointer(container)
est exécuté même si la condition avant ?
est vraie?
Et pourquoi avons-nous besoin de deux pour-les boucles?
ce serait cool si quelqu'un pouvait rendre les choses un peu plus claires pour moi!
1 réponses
la version GCC
le GCC est vraiment très simple. Tout d'abord, il est utilisé comme ceci:
Q_FOREACH(x, cont)
{
// do stuff
}
et qui sera étendu à
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
}
Alors tout d'abord:
for (QForeachContainer<__typeof__(cont)> _container_(cont); !_container_.brk && _container_.i != _container_.e; __extension__ ({ ++_container_.brk; ++_container_.i; }))
c'est la boucle réelle for
. Il met en place un QForeachContainer
pour aider avec l'itération. La variable brk
est intitialisée à 0. Puis la condition est vérifiée:
!_container_.brk && _container_.i != _container_.e
brk
est égal à zéro, donc !brk
est vrai, et on peut supposer que si le conteneur a des éléments i
(l'élément courant) n'est pas égal à e
(le dernier élément) pour le moment.
puis le corps de cette extérieure for
est entré, qui est:
for (variable = *_container_.i;; __extension__ ({--_container_.brk; break;}))
{
// do stuff
}
So x
est défini à *_container_.i
qui est l'élément courant sur lequel l'itération est effectuée, et il n'y a aucune condition donc probablement cette boucle va continuer pour toujours. Puis le corps de la boucle est entré, ce qui est notre code, et c'est juste un commentaire pour qu'il ne fasse rien.
puis la partie incrémentielle de la boucle intérieure est entrée, ce qui est intéressant:
__extension__ ({--_container_.brk; break;})
Il décrémente brk
de sorte que maintenant, -1, et les pauses de la boucle (avec __extension__
, ce qui rend GCC émettent pas de mises en garde pour l'utilisation de GCC extensions, comme vous maintenant savoir.)
puis la partie incrémentielle de la boucle extérieure est entrée:
__extension__ ({ ++_container_.brk; ++_container_.i; })
qui augmente brk
à nouveau et le rend 0 à nouveau, et puis i
est incrémenté de sorte que nous arrivons à l'élément suivant. La condition est vérifiée, et puisque brk
est maintenant 0 et i
n'égale probablement pas e
pourtant (si nous avons plus d'éléments) le processus est répété.
Pourquoi avons-nous décrété et puis incrémenter brk
comme ça? La raison en est que la partie incrémentielle de la boucle interne ne sera pas exécutée si nous avons utilisé break
dans le corps de notre code, comme ceci:
Q_FOREACH(x, cont)
{
break;
}
puis brk
serait encore 0 quand il sort de la boucle intérieure, et alors la partie incrémentielle de la boucle extérieure serait entré et incrémenter à 1, puis !brk
serait faux et l'état de la boucle extérieure évaluerait à faux, et l'avant - pourrait s'arrêter.
le truc est de réaliser qu'il y a deux for
Boucles; la durée de vie de l'extérieur est l'avant-main entière, mais l'intérieur ne dure que pour un élément. Il serait une boucle infinie puisqu'il n'a pas de condition, mais il est soit break
ed de par sa partie incrémentielle, ou par un break
dans le code que vous lui fournissez. C'est pourquoi x
semble comme il est assigné à "une seule fois" mais en fait il est assigné à chaque itération de la boucle externe.
the VS version
la version VS est un peu plus compliquée parce qu'elle doit contourner l'absence de L'extension GCC __typeof__
et des expressions-blocs, et la version de VS qu'elle a été écrite pour (6) n'avait pas auto
ou d'autres fonctionnalités fancy C++11.
regardons un exemple d'expansion pour ce que nous avons utilisé plus tôt:
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
}
le if(0){}else
est parce que VC++ 6 a fait le cadrage de for
variables fausses et une variable déclarée dans la partie d'initialisation d'une boucle for
pourrait être utilisée en dehors de la boucle. Donc c'est un contournement pour un bug VS. La raison pour laquelle ils ont fait if(0){}else
au lieu de juste if(0){...}
est donc que vous ne pouvez pas ajouter un else
après la boucle, comme
Q_FOREACH(x, cont)
{
// do stuff
} else {
// This code is never called
}
deuxièmement, regardons l'initialisation de l'extérieur for
:
const QForeachContainerBase &_container_ = qForeachContainerNew(cont)
la définition de QForeachContainerBase
est:
struct QForeachContainerBase {};
et la définition de qForeachContainerNew
est
template <typename T>
inline QForeachContainer<T>
qForeachContainerNew(const T& t) {
return QForeachContainer<T>(t);
}
et la définition de QForeachContainer
est
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); }
};
donc pour compenser le manque de __typeof__
(qui analogue au decltype
de C++11) nous devons utiliser le polymorphisme. La fonction qForeachContainerNew
renvoie un QForeachContainer<T>
en valeur mais en raison de extension de durée de vie des temporaires15191800920", si nous le stockons dans un const QForeachContainer&
, nous pouvons prolonger sa durée de vie jusqu'à la fin de l'extérieur for
(en fait le if
en raison du bug de VC6). Nous pouvons stocker un QForeachContainer<T>
dans un QForeachContainerBase
parce que le premier est une sous-classe du second, et nous devons en faire un renvoi comme QForeachContainerBase&
au lieu d'une valeur comme QForeachContainerBase
pour éviter de trancher.
Puis pour l'état de l'extérieur for
:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition();
la définition de qForeachContainer
est
inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
et la définition de qForeachPointer
est
template <typename T>
inline T *qForeachPointer(const T &) {
return 0;
}
c'est là que vous pourriez ne pas être au courant de ce qui se passe puisque ces fonctions semblent un peu inutiles. Voici comment ils fonctionnent et pourquoi vous en avez besoin:
nous avons un QForeachContainer<T>
stocké dans une référence à un QForeachContainerBase
sans aucun moyen de le retirer (que nous pouvons voir). Nous avons la lancer au bon type en quelque sorte, et c'est là que les deux fonctions sont en. Mais comment savoir à quel type le lancer?
une règle de l'opérateur ternaire x ? y : z
est que y
et z
doivent être du même type. Nous avons besoin de connaître le type de conteneur, donc nous utilisons la fonction qForeachPointer
pour faire cela:
qForeachPointer(cont)
le le type de déclaration qForeachPointer
est T*
, donc nous utilisons la déduction de type de modèle pour déduire le type du conteneur.
le true ? 0 : qForeachPointer(cont)
est d'être en mesure de passer un NULL
pointeur du type droit à qForeachContainer
de sorte qu'il saura quel type de mouler le pointeur que nous lui donnons. Pourquoi utiliser l'opérateur ternaire pour cela, au lieu de simplement faire qForeachContainer(&_container_, qForeachPointer(cont))
? C'est pour éviter d'évaluer cont
plusieurs fois. Le deuxième (en fait troisième) opérande pour ?:
n'est pas évalué à moins que la condition soit false
, et puisque la condition est true
elle-même, nous pouvons obtenir le bon type de cont
sans l'évaluer.
donc ça résout ça, et on utilise qForeachContainer
pour lancer _container_
vers le bon type. L'appel est:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))
et encore une fois la définition est
inline const QForeachContainer<T> *qForeachContainer(const QForeachContainerBase *base, const T *) {
return static_cast<const QForeachContainer<T> *>(base);
}
le second paramètre sera toujours NULL
parce que nous faisons true ? 0
qui évalue toujours à 0
, et nous utilisons qForeachPointer pour déduire le type T
, et utiliser cela pour lancer le premier argument à un QForeachContainer<T>*
de sorte que nous pouvons utiliser ses fonctions/variables membres avec la condition (toujours dans l'extérieur for
):
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
et condition
retourne:
(!brk++ && i != e)
qui est le même que la version GCC ci-dessus sauf qu'il incréments brk
après évaluation. Ainsi !brk++
est évalué à true
et puis brk
est augmenté à 1.
ensuite nous entrons dans le for
intérieur et commençons par l'initialisation:
x = *qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
qui ne définit la variable qu'à ce que l'itérateur i
pointe.
puis la condition:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
depuis brk
est 1, le corps de la boucle est entrée, ce qui est notre commentaire:
// stuff
puis l'incrément est entré:
--qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
qui décrète brk
retour à 0. Puis la condition est vérifiée à nouveau:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->brk
et brk
est 0 qui est false
et la boucle est sortie. Nous arrivons à la partie d'accroissement de l'extérieur for
:
++qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->i
et que les incréments i
à l'élément suivant. Puis nous arrivons à la condition:
qForeachContainer(&_container_, true ? 0 : qForeachPointer(cont))->condition()
qui vérifie que brk
est 0 (ce qu'il est) et l'incrémente à 1 encore, et le processus est répété si i != e
.
cela traite break
en code client seulement un peu différemment que la version GCC, puisque brk
ne sera pas décrémenté si nous utilisons break
dans notre code et il sera encore 1, et le condition()
sera être faux pour la boucle extérieure et la boucle extérieure sera break
.
et comme GManNickG a déclaré dans les commentaires, cette macro est un peu comme BOOST_FOREACH
de Boost que vous pouvez lire sur ici . Donc là vous l'avez, l'espérance qui vous aide.