J'ai une classe appelée Action
, qui est essentiellement un wrapper autour d'un deque d'objets Move
.
Parce que j'ai besoin de parcourir le deque de Moves
à la fois vers l'avant et vers l'arrière, j'ai un itérateur vers l'avant et un reverse_iterator comme variables membres de la classe. La raison en est que j'ai besoin de savoir quand j'ai dépassé la "fin" de la déque, à la fois quand je vais en avant ou en arrière.
La classe ressemble à ceci:
class Action
{
public:
SetMoves(std::deque<Move> & dmoves) { _moves = dmoves; }
void Advance();
bool Finished()
{
if( bForward )
return (currentfwd==_moves.end());
else
return (currentbck==_moves.rend());
}
private:
std::deque<Move> _moves;
std::deque<Move>::const_iterator currentfwd;
std::deque<Move>::const_reverse_iterator currentbck;
bool bForward;
};
La fonction Advance
est la suivante:
void Action::Advance
{
if( bForward)
currentfwd++;
else
currentbck++;
}
Mon problème est que je veux pouvoir récupérer un itérateur vers l'objet Move
courant, sans avoir à me demander si je vais en avant ou en arrière. Cela signifie qu'une fonction retourne un type d'itérateur, mais j'ai deux types.
Dois-je oublier de retourner un itérateur et renvoyer une référence const à un objet Move
à la place?
meilleurs vœux,
BeeBand
C'est exactement le genre de problème qui a incité la conception de STL à commencer. Il y a de vraies raisons pour:
Je soupçonne que ce que vous voyez en ce moment est plus ou moins la pointe de l'iceberg des vrais problèmes. Mon conseil serait de prendre du recul, et au lieu de demander comment traiter les détails de la conception telle qu'elle est actuellement, posez une question un peu plus générale sur ce que vous essayez d'accomplir et sur la meilleure façon d'y parvenir. résultat final.
Pour ceux qui se soucient principalement de la question dans le titre, la réponse est un "oui" fortement qualifié. En particulier, un reverse_iterator a un membre base()
pour ce faire. Les qualifications sont cependant quelque peu problématiques.
Pour illustrer le problème, considérez le code comme ceci:
#include <iostream>
#include <vector>
#include <iterator>
int main() {
int i[] = { 1, 2, 3, 4};
std::vector<int> numbers(i, i+4);
std::cout << *numbers.rbegin() << "\n";
std::cout << *numbers.rbegin().base() << "\n";
std::cout << *(numbers.rbegin()+1).base() << "\n";
std::cout << *numbers.rend() << "\n";
std::cout << *numbers.rend().base() << "\n";
std::cout << *(numbers.rend()+1).base() << "\n";
}
L'exécuter à ce moment particulier sur ma machine particulière produit la sortie suivante:
4
0
4
-1879048016
1
-1879048016
Résumé: avec rbegin()
we must ajoutez-en un avant de le convertir en itérateur de transfert pour obtenir un itérateur valide - mais avec rend()
nous devons not ajoutez-en un avant de convertir pour obtenir un itérateur valide.
Tant que vous utilisez X.rbegin()
et X.rend()
comme paramètres d'un algorithme générique, c'est bien - mais l'expérience indique que la conversion en itérateurs de transfert entraîne souvent des problèmes.
En fin de compte, cependant, pour le corps de la question (par opposition au titre), la réponse est à peu près celle donnée ci-dessus: le problème vient de la tentative de créer un objet qui combine la collection avec quelques itérateurs dans cette collection . Résolvez ce problème et toute l'entreprise avec des itérateurs avant et arrière devient théorique.
Les itérateurs inverses ont un membre base()
qui renvoie un itérateur direct correspondant. Attention, ce n'est pas un itérateur qui se réfère au même objet - il se réfère en fait à l'objet suivant de la séquence. C'est ainsi que rbegin()
correspond à end()
et rend()
correspond à begin()
.
Donc, si vous voulez retourner un itérateur, vous feriez quelque chose comme
std::deque<Move>::const_iterator Current() const
{
if (forward)
return currentfwd;
else
return (currentbck+1).base();
}
Je préférerais cependant renvoyer une référence et encapsuler tous les détails de l'itération à l'intérieur de la classe.
Puisque std::deque
est un conteneur d'accès aléatoire (identique à std::vector
) il vaut mieux utiliser un seul index entier dans le deque pour les deux traversées.
Il me semble que vous avez en fait deux comportements différents dans la même classe.
Notamment, il semble que vous ne pouvez parcourir votre collection que dans un seul ordre, sinon si vous deviez commencer la traversée puis changer l'argument bforward
vous vous retrouveriez avec une situation assez étrange.
Personnellement, je suis tout à fait d'accord pour exposer les deux itérateurs (c'est-à-dire avant begin, end, rbegin and rend
).
Vous pouvez également renvoyer un simple objet Iterator:
template <class T>
class Iterator
{
public:
typedef typename T::reference_type reference_type;
Iterator(T it, T end) : m_it(it), m_end(end) {}
operator bool() const { return m_it != m_end; }
reference_type operator*() const { return *m_it; }
Iterator& operator++() { ++m_it; return *this; }
private:
T m_it;
T m_end;
};
template <class T>
Iterator<T> make_iterator(T it, T end) { return Iterator<T>(it,end); }
Ensuite, vous pouvez simplement renvoyer cet objet simple:
class Action
{
public:
Action(std::deque<Move> const& d): m_deque(d) {} // const& please
typedef Iterator< std::deque<Move>::iterator > forward_iterator_type;
typedef Iterator< std::deque<Move>::reverse_iterator > backward_iterator_type;
forward_iterator_type forward_iterator()
{
return make_iterator(m_deque.begin(), m_deque.end());
}
backward_iterator_type backward_iterator()
{
return make_iterator(m_deque.rbegin(), m_deque.rend());
}
private:
std::deque<Move> m_deque;
};
Ou si vous voulez choisir dynamiquement entre la traversée avant et arrière, vous pouvez faire d'Iterator une interface virtuelle pure et avoir une traversée avant et arrière.
Mais vraiment, je ne vois pas vraiment l'intérêt de stocker les deux itérateurs avant et arrière s'il semble que vous n'en utiliserez qu'un: /
Vous devriez peut-être repenser votre choix de conteneur.
Habituellement, vous n'avez pas besoin d'utiliser des itérateurs inverses pour revenir en arrière,
currentfwd--
ira en arrière, bien que cela puisse ne pas fonctionner (que je suppose que vous avez essayé) avec dequeue.
Ce que vous devez vraiment faire, c'est modéliser votre classe ici en tant que décorateur de file d'attente et implémenter vos propres itérateurs d'action. Ce serait ce que je ferais de toute façon.