2017-09-26 5 views
1

J'ai des classes qui utilisent habituellement des conteneurs standards comme champs sous-jacents. Par exemple, j'ai une classeComment implémenter des itérateurs standards dans la classe

template <typename T> 
class Vec_3D 
{ 
public: 
    /* ... */ 
    std::array<T, 3> vec; 
    /* ... */ 
}; 

qui n'a qu'une seule variable vec et le reste ne sont que des fonctions dont j'ai besoin lorsque vous travaillez avec des vecteurs. Je veux être en mesure d'utiliser en fonction de gamme tels que la boucle

Vec_3D<double> vec; 
for (double val : vec) {/*...*/} 

qui devrait obviusly itérer sur std::array<double, 3>.

Comment implémenter des itérateurs dans ma classe qui devrait à son tour appeler des itérateurs de std::array<T, 3>?

J'ai commencé avec this question et a essayé de définir des itérateurs dans ma classe comme

typedef std::iterator<std::random_access_iterator_tag, T, ptrdiff_t, T*, T&> iterator; 
typedef std::iterator<std::random_access_iterator_tag, const T, ptrdiff_t, const T*, const T&> const_iterator; 

inline iterator begin() noexcept { return vec.begin(); } 
inline const_iterator cbegin() const noexcept { return vec.cbegin(); } 
inline iterator end() noexcept { return vec.end(); } 
inline const_iterator cend() const noexcept { return vec.end(); } 

mais nous avons eu des erreurs de compilation

error: no match for ‘operator!=’ (operand types are ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’ and ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’) 

et operator++, operator*

+1

'std :: itérateur' n'est pas ce que vous pensez qu'il est –

Répondre

4

Une gamme à base de boucle exige seulement que votre classe ont begin() et end() méthodes (ou de std::begin() et surcharges std::end()) qui retournent itérateurs. Il ne se soucie pas d'où viennent ces itérateurs. Ainsi, la solution la plus simple est d'utiliser simplement propres itérateurs du tableau au lieu d'essayer de définir vos propres, par exemple:

template <typename T> 
class Vec_3D 
{ 
public: 
    typedef typename std::array<T, 3> array_type; 
    typedef typename array_type::iterator iterator; 
    typedef typename array_type::const_iterator const_iterator; 
    // or: 
    // using array_type = std::array<T, 3>; 
    // using iterator = array_type::iterator; 
    // using const_iterator = array_type::const_iterator; 
    ... 

    inline iterator begin() noexcept { return vec.begin(); } 
    inline const_iterator cbegin() const noexcept { return vec.cbegin(); } 
    inline iterator end() noexcept { return vec.end(); } 
    inline const_iterator cend() const noexcept { return vec.end(); } 
    ... 

private: 
    array_type vec; 
}; 
+0

Vous devriez expliquer pourquoi' std :: itérateur' n'a pas fonctionné –

2

std :: iterator est une classe de base seulement, c'est essentiellement un conteneur pour certains traits, mais si vous vouliez l'utiliser pour implémenter votre propre classe d'itérateur, vous devriez en dériver .

Cependant vous n'avez pas besoin de l'utiliser, il y avait une proposition pour le déprécier, vous pouvez simplement définir ces traits directement dans un itérateur que vous écrivez. La question suivante a l'information sur la proposition et aider à mettre en œuvre une classe iterator: - Preparation for std::iterator Being Deprecated

Au moment vous définissez les types iterator de votre conteneur en utilisant cette base, pas une classe qui peut réellement faire une itérer, qui est pourquoi ça échoue.

Vous exposer la baie en tant que membre public. Si vous êtes heureux d'exposer que votre vec_3d est implémenté en utilisant un tableau (que vous continuiez à exposer le tableau de membres publiquement ou non), vous pouvez simplement utiliser les itérateurs du tableau - il n'est pas clair que votre itérateur ait besoin d'un comportement sur mesure juste parce que votre conteneur ajoute des fonctionnalités.

3

std::iterator est (était) un type d'aide pour définir les typedef s qu'un iterator typique exige. Ces typedefs dans la classe à son tour font std::iterator_traits travailler avec votre itérateur.

Cependant, il n'implémente pas réellement les opérations requises pour vous. Il était déconseillé, car le comité std n'aimait pas spécifier que les itérateurs standards devaient avoir ces typedefs, et écrire le typedefs n'était pas beaucoup plus volumineux que de déterminer quels arguments passer au template std::iterator.

La chose facile à faire ici est de voler l'itérateur de votre conteneur sous-jacent. Cela fait fuir votre abstraction, mais c'est efficace et facile.

template <typename T> 
struct Vec_3D { 
    using container=std::array<T, 3>; 
    using iterator=typename container::iterator; 
    using const_iterator=typename container::const_iterator; 

    iterator begin() { return vec.begin(); } 
    iterator end() { return vec.end(); } 
    const_iterator begin() const { return vec.begin(); } 
    const_iterator end() const { return vec.end(); } 
private: 
    /* ... */ 
    container vec; 
    /* ... */ 
}; 

Si vous ne voulez pas exposer votre type de conteneur sous-jacent, si vous êtes prêt à garantir votre conteneur sous-jacent est un tampon contigu que vous pouvez faire:

template <typename T> 
struct Vec_3D { 
    using iterator=T*; 
    using const_iterator=T const*; 

    iterator begin() { return vec.data(); } 
    iterator end() { return vec.data()+vec.size(); } 
    const_iterator begin() const { return vec.data(); } 
    const_iterator end() const { return vec.data()+vec.size(); } 
private: 
    /* ... */ 
    std::array<T,3> vec; 
    /* ... */ 
}; 

comme pointeurs sont itérateurs valides.

Si vous trouvez que vous écrivez « je suis un conteneur modifié » boilerplate trop, vous pouvez automatiser:

template<class Container> 
struct container_wrapper { 
    using container=Container; 

    using iterator=typename container::iterator; 
    using const_iterator=typename container::const_iterator; 

    iterator begin() { return m_data.begin(); } 
    iterator end() { return m_data.end(); } 
    const_iterator begin() const { return m_data.begin(); } 
    const_iterator end() const { return m_data.end(); } 
protected: 
    Container m_data; 
}; 

puis

template <typename T> 
class Vec_3D:private container_wrapper<std::array<T,3>> { 
    // ... 
}; 

mais même cela pourrait être un peu bien, pourquoi pas seulement:

template <typename T> 
class Vec_3D:public std::array<T,3> { 
    // ... 
}; 

Il est vrai que la suppression Vec_3D par un pointeur vers ba se est un comportement indéfini, mais qui supprime les pointeurs vers des conteneurs standards?

Si cela vous inquiète:

template <typename T> 
class Vec_3D: private std::array<T,3> { 
    using container = std::array<T,3>; 
    using container::begin(); 
    using container::end(); 
    // ... 
}; 

vous permet héritez privé, puis apporter certaines opérations de retour dans le périmètre.

+1

@bob oui j'ai eu. – Yakk