web-dev-qa-db-fra.com

Puis-je convertir un itérateur inversé en itérateur direct?

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

43
BeeBand

C'est exactement le genre de problème qui a incité la conception de STL à commencer. Il y a de vraies raisons pour:

  1. Ne pas stocker d'itérateurs avec des conteneurs
  2. Utilisation d'algorithmes acceptant des itérateurs arbitraires
  3. Avoir des algorithmes pour évaluer une plage entière au lieu d'un seul élément à la fois

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.

27
Jerry Coffin

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.

65
Mike Seymour

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.

6
Nikolai Fetissov

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: /

1
Matthieu M.

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.

0
Charles