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

StackOverflow https://stackoverflow.com/questions/470835

Question

Après avoir joué avec cela, je soupçonne que ce n’est pas 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 souhaite ajouter ces méthodes au 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, je souhaite essentiellement fournir une classe qui semble itérer sur une collection (que l'appelant de Begin () et End () n'est pas autorisée à voir) de pointeurs IInterface, mais qui itère en réalité sur une collection de pointeurs vers d'autres objets (privés de la classe Container) pouvant être convertis en pointeurs IInterface.

Quelques points clés:

  • MagicIterator doit être défini en dehors de Container.
  • Container::Item doit rester privé.
  • IInterface doit parcourir plus de std::list<Container::Item> pointeurs, même si Object* contient un Object. IInterface* contient un std::vector<SomeOtherItem>, et mylist<YetAnotherItem> peut être utilisé pour récupérer new().
  • malloc() doit être réutilisable avec plusieurs classes ressemblant à Container, mais peut avoir en interne différentes implémentations de listes contenant des objets différents (memcpy(), <=>) et avec <=> obtenu de manière différente à chaque fois.
  • <=> ne devrait 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 en dur dans des conteneurs particuliers à l'intérieur de <=> (ainsi, le compilateur le résout automatiquement, par exemple ).
  • La solution doit être compilée sous Visual C ++ sans utiliser d'autres bibliothèques (telles que boost), ce qui nécessiterait un contrat de licence de leurs auteurs.
  • De plus, l'itération ne peut allouer aucune mémoire de tas (donc pas de <=> ou <=> à aucun moment), ni de <=>.

Merci de votre temps, même si vous ne faites que lire; celui-ci m'a vraiment dérangé!

Mise à jour: Bien que j'aie eu des réponses très intéressantes, aucune n'a encore satisfait à toutes les exigences ci-dessus. Les zones les plus délicates sont notamment: i) le découplage de MagicIterator de Container (les arguments du modèle par défaut ne le coupent pas), et ii) l’évitement de l’allocation de tas; mais je suis vraiment après une solution qui couvre toutes les balles ci-dessus.

Était-ce utile?

La solution 6

J'ai maintenant trouvé une solution adaptée à mon objectif initial. Je ne l’aime toujours pas:)

La solution implique que MagicIterator soit basé sur un modèle sur IInterface * et construit avec un vide * sur un itérateur, la taille en octets dudit itérateur et une table de pointeurs sur des fonctions qui effectuent des fonctions d'itération standard sur ledit vide *, telles que l'incrément , décrémenter, déréférencer, etc. MagicIterator suppose qu’il est prudent de mémoriser l’itérateur donné dans un tampon interne et implémente ses propres membres en transmettant son propre tampon en tant que vide * aux fonctions fournies comme s’il s’agissait de l’itérateur initial.

Le conteneur doit alors implémenter des fonctions d'itération statiques qui convertissent un void * fourni en 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 enfreint ma règle originale concernant & "no memcpy () &"; et émet des hypothèses sur les éléments internes des itérateurs en question. Mais cela évite l’allocation de tas, garde les éléments internes de Collection (y compris Item) privés, rend MagicIterator totalement indépendant de la collection en question et de IInterface *, et permet en théorie à MagicIterators de travailler avec n’importe quelle collection (à condition que ses itérateurs puissent être mémorisés () '. d).

Autres conseils

Je pense que vous avez deux problèmes distincts ici:

Créez d’abord un itérateur qui renverra 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 devez créer ce type comme étant interne dans Container et y revenir à partir de ses méthodes begin() et end().

Deuxièmement, vous voulez le polymorphisme d'exécution MagicIterator. C'est exactement ce que any_iterator fait. le MagicIterator<IInterface*> est juste any_iterator<IInterface*, boost::forward_traversal_tag, IInterface*>, et cont_iter peut seulement lui être attribué.

Cela ne semble pas trop compliqué. Vous pouvez définir l'itérateur à l'extérieur. Vous pouvez également utiliser des typedefs. Quelque chose comme ça irait je pense. Notez que cela serait beaucoup plus propre si ce MagicIterator n'était pas un modèle gratuit, mais un membre de Item, dactylographié dans Container. Comme il est maintenant, il y a une référence cyclique dedans, ce qui rend nécessaire l'écriture d'un code de contournement moche.

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 mixeur d'itérateurs fourni par boost, qui fonctionne comme la version d'entrée de boost :: function_output_iterator. Il appelle votre itérateur operator() qui renvoie ensuite la valeur appropriée, en procédant comme précédemment dans notre operator* principe. Vous le trouvez dans random/detail/iterator_mixin.hpp. Cela entraînerait probablement moins de code. Mais cela nécessite également de nous serrer la tête pour nous assurer que tout est bien placé entre amis, car Item est privé et que l’itérateur n’est pas défini à l’intérieur de Item. Quoi qu'il en soit, bonne chance :)

Créer un résumé 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 Iterator classe à intégrer:

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 créer une Container classe & à l'intérieur de & des pour garder les choses en ordre.)

Dans votre ctor classe, renvoyez une instance de <=> avec une sous-classe personnalisée de <=> dans le <=>:

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);
}

Cela dépend vraiment de Container, car les valeurs de retour c.Begin() et c.End() sont définies par la mise en oeuvre.

Si une liste de MagicIterator s possibles est connue de <=>, une classe wrapper peut ê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 facilité consisterait à utiliser un modèle qui accepte le 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.

Je ne vois aucune raison pour laquelle vous ne pouvez pas appliquer cela exactement comme vous l'avez expliqué ... est-ce qu'il me manque quelque chose?

Pour clarifier, vous aurez besoin de mettre une sorte de méthode d’accesseur sur votre classe Container. 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 les exposerais directement. Ces méthodes d'accès utiliseraient un itérateur STL normal à l'intérieur de Container et effectueraient la conversion en IInterface. Ainsi, l'itération serait en réalité effectuée avec les méthodes d'accès du conteneur et MagicIterator serait simplement une sorte d'objet proxy pour faciliter la tâche. Pour le rendre réentrant, vous pouvez faire en sorte que MagicIterator transmette une sorte d’ID permettant de rechercher l’itérateur STL dans le conteneur, ou le transmettre dans l’itérateur STL en tant que void *.

Un visiteur peut être une solution plus simple (et donc plus facile à gérer).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top