Un adaptateur d'itérateur C++ qui enveloppe et cache un itérateur interne et convertit le type itéré

ayant joué avec ça, je pense que ce n'est pas du tout possible, mais j'ai pensé que je demanderais aux experts. J'ai le code C++ suivant:

class IInterface
{
    virtual void SomeMethod() = 0;
};

class Object
{
    IInterface* GetInterface() { ... }
};

class Container
{
private:
    struct Item
    {
        Object* pObject;
        [... other members ...]
    };
    std::list<Item> m_items;
};

je veux ajouter ces méthodes à conteneur:

    MagicIterator<IInterface*> Begin();
    MagicIterator<IInterface*> End();

pour que les appelants puissent écrire:

Container c = [...]
for (MagicIterator<IInterface*> i = c.Begin(); i != c.End(); i++)
{
    IInterface* pItf = *i;
    [...]
}

donc essentiellement je veux fournir une classe qui semble itérer sur une certaine collection (que l'appelant de Begin () et End () n'est pas autorisé à voir) de iInterface pointeurs, mais qui est en fait itérating au-dessus d'une collection de pointeurs à d'autres objets (privé à la classe de conteneur) qui peut être converti en iInterface pointeurs.

quelques points clés:

  • MagicIterator doit être défini à l'extérieur de Container .
  • Container::Item doit rester privé.
  • MagicIterator doit itérer IInterface pointeurs, malgré le fait que Container tient un std::list<Container::Item> . Container::Item contient un Object* , et Object peut être utilisé pour aller chercher IInterface* .
  • MagicIterator doit être réutilisable avec plusieurs classes qui ressemblent à des conteneurs, mais peut avoir à l'interne différentes implémentations de liste contenant des objets différents ( std::vector<SomeOtherItem> , mylist<YetAnotherItem> ) et avec IInterface* obtenus de manière différente à chaque fois.
  • MagicIterator ne doit pas contenir de code spécifique au conteneur, bien qu'il puisse déléguer à des classes qui le font, à condition que cette délégation ne soit pas codée de manière difficile à des conteneurs particuliers à l'intérieur de MagicIterator (ainsi est résolu d'une manière ou d'une autre automatiquement par le compilateur, par exemple).
  • la solution doit compiler sous Visual C++ sans utiliser d'autres bibliothèques (comme boost) qui nécessiteraient une licence l'accord de leurs auteurs.
  • de plus, l'itération ne peut pas allouer de mémoire tas (donc pas de new() ou malloc() à n'importe quel stade), et pas de memcpy() .

Merci pour votre temps, même si vous ne faites que lire; celui-ci m'a vraiment embêté!

mise à jour: bien que j'aie eu des réponses très intéressantes, aucune n'a encore répondu à toutes les exigences ci-dessus. Notamment les zones délicates sont I) découplage MagicIterator de conteneur d'une manière ou d'une autre (les arguments de modèle par défaut ne le coupent pas), et ii) éviter l'allocation de tas; mais je suis vraiment après une solution qui couvre tous les points ci-dessus.

12
demandé sur El Zorko 2009-01-23 00:17:55

7 réponses

je pense que vous avez deux questions se posent ici:

tout d'abord, créez un itérateur qui retournera le IInterface* de votre list<Container::Item> . Cela se fait facilement avec boost::iterator_adaptor :

class cont_iter
  : public boost::iterator_adaptor<
        cont_iter                       // Derived
      , std::list<Container::Item>::iterator // Base
      , IInterface*                     // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
      , IInterface*                     // Reference :)
    >
{
 public:
    cont_iter()
      : cont_iter::iterator_adaptor_() {}

    explicit cont_iter(const cont_iter::iterator_adaptor_::base_type& p)
      : cont_iter::iterator_adaptor_(p) {}

 private:
    friend class boost::iterator_core_access;
    IInterface* dereference() { return this->base()->pObject->GetInterface(); }
};

vous créeriez ce type comme intérieur dans Container et retourner dans de ses begin() et end() méthodes.

Deuxièmement, vous voulez le runtime-polymorphe MagicIterator . C'est exactement ce que any_iterator faire. le MagicIterator<IInterface*> est juste any_iterator<IInterface*, boost::forward_traversal_tag, IInterface*> , et cont_iter peut être juste assigné à lui.

13
répondu jpalecek 2009-01-23 17:07:10

N'a pas l'air trop compliqué. Vous pouvez définir l'itérateur à l'extérieur. Vous pouvez également utiliser des typedefs. Quelque chose comme ça conviendrait je pense. Notez qu'il serait beaucoup plus propre si ce MagicIterator serait pas un gabarit libre, mais un membre de L'article, dactylographié en conteneur peut-être. Comme il est maintenant, il y a une référence cyclique en elle, qui le rend nécessaire d'écrire un code de contournement laid.

namespace detail {
    template<typename T, typename U>
    struct constify;

    template<typename T, typename U>
    struct constify<T*, U*> {
        typedef T * type;
    };

    template<typename T, typename U>
    struct constify<T*, U const*> {
        typedef T const * type;
    };
}

template<typename DstType, 
         typename Container,
         typename InputIterator>
struct MagicIterator;

class Container
{
private:
    struct Item
    {
        Object* pObject;
    };

    std::list<Item> m_items;

public:

    // required by every Container for the iterator
    typedef std::list<Item> iterator;
    typedef std::list<Item> const_iterator;

    // convenience declarations
    typedef MagicIterator< IInterface*, Container, iterator > 
        item_iterator;
    typedef MagicIterator< IInterface*, Container, const_iterator > 
        const_item_iterator;

    item_iterator Begin();
    item_iterator End();
};

template<typename DstType, 
         typename Container = Container,
         typename InputIterator = typename Container::iterator>
struct MagicIterator : 
    // pick either const T or T, depending on whether it's a const_iterator.
    std::iterator<std::input_iterator_tag, 
                  typename detail::constify<
                           DstType, 
                           typename InputIterator::value_type*>::type> {
    typedef std::iterator<std::input_iterator_tag, 
                 typename detail::constify<
                          DstType, 
                          typename InputIterator::value_type*>::type> base;
    MagicIterator():wrapped() { }
    explicit MagicIterator(InputIterator const& it):wrapped(it) { }
    MagicIterator(MagicIterator const& that):wrapped(that.wrapped) { }

    typename base::value_type operator*() {
        return (*wrapped).pObject->GetInterface();
    }

    MagicIterator& operator++() {
        ++wrapped;
        return *this;
    }

    MagicIterator operator++(int) {
        MagicIterator it(*this);
        wrapped++;
        return it;
    }

    bool operator==(MagicIterator const& it) const {
        return it.wrapped == wrapped;
    }

    bool operator!=(MagicIterator const& it) const {
        return !(*this == it);
    }

    InputIterator wrapped;
};

// now that the iterator adepter is defined, we can define Begin and End
inline Container::item_iterator Container::Begin() {
    return item_iterator(m_items.begin());
}

inline Container::item_iterator Container::End() {
    return item_iterator(m_items.end());
}

maintenant, commencez à l'utiliser:

for(MagicIterator<IInterface*> it = c.Begin(); it != c.End(); ++it) {
    // ...
}

vous pouvez également utiliser un mixin itérateur fourni par boost, qui fonctionne comme la version d'entrée de boost::function_output_iterator. Il appelle votre iterator operator() qui retourne alors la valeur appropriée, faisant ce que nous faisons ci-dessus dans notre operator* en principe. Vous le trouverez dans random/detail/iterator_mixin.hpp . Cela se traduirait probablement par moins de code. Mais il faut également se tordre le cou pour s'assurer que l'ami-truc parce que L'article est privé et l'itérateur n'est pas défini à l'intérieur de L'article. Quoi qu'il en soit, bonne chance:)

4
répondu Johannes Schaub - litb 2009-01-22 23:43:35

créer un abrégé IteratorImplementation classe:

template<typename T>
class IteratorImplementation
{
    public:
        virtual ~IteratorImplementation() = 0;

        virtual T &operator*() = 0;
        virtual const T &operator*() const = 0;

        virtual Iterator<T> &operator++() = 0;
        virtual Iterator<T> &operator--() = 0;
};

et une classe Iterator pour l'envelopper:

template<typename T>
class Iterator
{
    public:
        Iterator(IteratorImplementation<T> * = 0);
        ~Iterator();

        T &operator*();
        const T &operator*() const;

        Iterator<T> &operator++();
        Iterator<T> &operator--();

    private:
        IteratorImplementation<T> *i;
}

Iterator::Iterator(IteratorImplementation<T> *impl) :
    i(impl)
{
}

Iterator::~Iterator()
{
    delete i;
}

T &Iterator::operator*()
{
    if(!impl)
    {
        // Throw exception if you please.
        return;
    }

    return (*impl)();
}

// etc.

(Vous pouvez le faire IteratorImplementation classe "à l'intérieur" de Iterator pour garder les choses en ordre.)

dans votre classe Container , retournez une instance de Iterator avec une sous-classe personnalisée de IteratorImplementation dans le ctor :

class ObjectContainer
{
    public:
        void insert(Object *o);
        // ...

        Iterator<Object *> begin();
        Iterator<Object *> end();

    private:
        class CustomIteratorImplementation :
            public IteratorImplementation<Object *>
        {
            public:
                // Re-implement stuff here.
        }
};

Iterator<Object *> ObjectContainer::begin()
{
    CustomIteratorImplementation *impl = new CustomIteratorImplementation();  // Wish we had C++0x's "var" here.  ;P

    return Iterator<Object *>(impl);
}
4
répondu strager 2009-01-22 23:55:48

cela dépend vraiment du Container , parce que les valeurs de retour de c.Begin() et c.End() sont définies par la mise en œuvre.

si une liste de possibles Container s est connue de MagicIterator , une classe d'emballage pourrait être utilisée.

template<typename T>
class MagicIterator
{
    public:
        MagicIterator(std::vector<T>::const_iterator i)
        {
            vector_const_iterator = i;
        }

        // Reimplement similarly for more types.
        MagicIterator(std::vector<T>::iterator i);
        MagicIterator(std::list<T>::const_iterator i);
        MagicIterator(std::list<T>::iterator i);

        // Reimplement operators here...

    private:
        std::vector<T>::const_iterator vector_const_iterator;
        std::vector<T>::iterator       vector_iterator;
        std::list<T>::const_iterator   list_const_iterator;
        std::list<T>::iterator         list_iterator;
};

la voie facile serait d'utiliser un gabarit qui accepte Container 'S type:

// C++0x
template<typename T>
class Iterator :
    public T::iterator
{
    using T::iterator::iterator;
};

for(Iterator<Container> i = c.begin(); i != c.end(); ++i)
{
    // ...
}

plus d'informations ici.

2
répondu strager 2017-05-23 10:27:32

Je ne vois pas pourquoi vous ne pouvez pas mettre en œuvre ceci exactement comme vous l'avez prévu... ai-je raté quelque chose?

pour clarifier, vous aurez besoin de mettre une sorte de méthodes accessor sur votre classe de conteneur. Ils peuvent être privés et vous pouvez déclarer MagicIterator comme un ami, si vous pensez que c'est la meilleure façon de l'encapsuler, mais je voudrais exposer directement. Ces méthodes d'accesseurs utiliseraient un itérateur STL normal à L'intérieur du conteneur et effectueraient la conversion en IInterface. Ainsi l'itération serait en fait faite avec les méthodes accessor du conteneur et MagicIterator serait juste une sorte d'objet proxy pour le rendre plus facile. Pour le rendre réentrant, vous pouvez faire passer le MagicIterator dans une sorte d'ID pour chercher L'itérateur STL à l'intérieur du conteneur, ou vous pouvez en fait le faire passer dans l'itérateur STL comme un void * .

1
répondu rmeador 2009-01-22 21:25:35

j'ai maintenant trouvé une solution qui est fitter pour mon but original. Je ne l'aime toujours pas bien :)

la solution implique que MagicIterator soit Templé sur IInterface* et construit avec à la fois un vide* à un itérateur, la taille de byte dudit itérateur, et un tableau de pointeurs vers des fonctions qui effectuent des fonctions d'itération standard sur ledit vide* telles que l'incrément, le décrément, la déréférence, etc. MagicIterator suppose qu'il est sûr à memcpy le donné iterator dans un tampon interne, et implémente ses propres membres en passant son propre tampon comme un vide* aux fonctions fournies comme si c'était l'itérateur original.

conteneur doit alors mettre en œuvre des fonctions d'itération statique qui rejettent un vide fourni* à un std::list::iterator. Container:: begin() et Container:: end () construisent simplement un std::list::iterator, lui passent un pointeur dans un MagicIterator avec une table de ses fonctions d'itération, et renvoient le MagicIterator.

c'est un peu dégoûtant, et brise ma règle originale concernant" no memcpy ()", et fait des suppositions sur les internes des itérateurs en question. Mais il évite l'allocation de tas, garde les internes de Collection (y compris L'article) privés, rend MagicIterator entièrement indépendant de la collection en question et de L'interface*, et en théorie permet MagicIterators à travailler avec n'importe quelle collection (à condition que ses itérateurs peuvent être sans danger memcopy()'d).

0
répondu El Zorko 2009-01-24 18:12:57

un visiteur peut être une solution plus simple (et donc plus facile à entretenir).

0
répondu Andrew 2009-12-27 08:16:10