Wrap STL iterator use references instead of pointers

I have a class like Point

:

class Point {
public:
    double x, y;
};

      

And I have a container, for example PointCloud

, that wraps inside std::vector<Point*>

:

class PointCloud {
public:
   typedef std::vector<Point*>::const_iterator iterator;
   iterator begin();
   iterator end();
private:
  std::vector<Point*> _point
};

      

Now I can use my class like this:

PointCloud cloud;
for(auto point : cloud) {
  std::cout << point->x << std::endl;
}    

      

I want to implement begin()

and end()

, so my iterators return references instead of pointers, and I could write my code this way:

PointCloud cloud;
for (auto point : cloud) {
  std::cout << point.x << std::endl;
}  

      

Note: operator .

instead of operator->

I have some reasons for this:

  • Compatibility: First, I write my class with std::vector<Point> _point

    , and if I change my mind in the future and replace it with std::vector<Point*> _point

    , the users of my class won't need to change anything in their code.

  • it's just safer to work with links.

  • There is no need to type ->

    instead .

    .

I work with links instead of pointers.

Is it the right thing to do? If so, what would be the best way to implement it?

Finally I found the right solution: boost :: indirect_iterator http://www.boost.org/doc/libs/1_57_0/libs/iterator/doc/indirect_iterator.html

#include <memory>
#include <vector>
#include <iostream>
#include <boost/iterator/indirect_iterator.hpp>

class Point {
public:
    Point(double x, double y) : x(x), y(y) {};
    double x, y;
};

class PointCloud {
public:
    using point_vector = std::vector<std::unique_ptr<Point>>;
    using const_iterator = boost::indirect_iterator<point_vector::const_iterator>;
    const_iterator begin() { return _points.begin(); }
    const_iterator end() { return _points.end(); }
    void insert(double x, double y) { _points.emplace_back(new Point(x, y)); }
private:
    point_vector _points;
};

int main()
{
    PointCloud points;
    points.insert(2, 3);
    points.insert(4, 5);
    for (auto &point : points)
        std::cout << point.x << ' ' << point.y << std::endl;
    return 0;
}

      

+3


source to share


2 answers


Assuming you have access to the lvalues ​​you want to expose, the generic one is deref_iterator<It>

fairly straightforward to create, although it's a bit of a typing exercise. To cover all categories of iterators, you need to go through all operations except, obviously, any element access, i.e. Operators *

, ->

and []

. In addition to the operations, you will also need to provide the appropriate characteristics of the iterator. I don't think there is an easy trick to avoid this.

The corresponding iterator might look something like this (it doesn't compile and almost certainly skips some operations):

template <typename It>
class deref_iterator {
    It it;
    using traits     = std::iterator_traits<It>;
    using base_value = typename traits::value_type;
public:
    using value_type        = std::decay_t<decltype(*std::declval<base_value>())>;
    using reference         = value_type&;
    using pointer           = value_type*;
    using difference_type   = typename traits::difference_type;
    using iterator_category = typename traits::iterator_category;

    deref_iterator(): it() {}
    explicit deref_iterator(It it): it(it) {}

    auto operator*() -> reference { return **this->it; }
    auto operator->() -> pointer { return &**this->it; }
    auto operator[](difference_type index) -> reference { return *this->it[index]; }

    auto operator++() -> deref_iterator& { ++this->it; return *this; }
    auto operator++(int) -> deref_iterator { auto rc(*this); ++this->it; return rc; }

    bool operator== (deref_iterator const& other) const { return this->it == other.it; }
    bool operator!= (deref_iterator const& other) const { return !(*this == other); }
    // more in the same spirit for bidirectional and andom access iterator
};

      

I think the above should be enough to handle subsequent iteration. Getting more powerful iterators is just code.



It's worth noting that any mutations affecting the sequence will not move pointers, but point to values. That is, sequence mutation will not work with such an iterator, since the values ​​will be sliced ​​(if there are copy operations available, if they are not available, you will get a compile-time error). The main problem is that STL only considers one object at given locations, whereas conceptually at least two:

  • Available actual value.
  • An object containing the value.

For value types, the two objects are identical, although a single value conceptually still plays two roles. For a sequence like dereferenced objects, the value is the type of the value (for example Point

), while the holding objects are the object pointing to those objects (for example Point*

). If they can be highlighted as needed, the STL can be slightly more powerful.

+4


source


If your problem is with iteration only, as you are only talking about looping and printing. In the meantime, there is no need to know about it. ”

Why not have two overloads by taking vector<Point*>

and the other vector<Point>

.

template<Func&& f>
void LoopThrough(std::vector<Point*> const& v, Func&& f)
{
    for (auto p : v)
    {
        f(p);
    }
}

template<Func&& f>
void LoopThrough(std::vector<Point> const& v, Func&& f)
{
    for (auto p : v)
    {
        f(p);
    }
}

      

Then keep changing your typedef to suit your needs.



class PointCloud {
public:
    typedef std::vector<Point*>::const_iterator iterator;
    // typedef std::vector<Point>::const_iterator iterator;
    typedef std::vector<Point*> Vec;
    //typedef std::vector<Point> Vec;

    iterator begin()
    {
        return _point.begin();
    }
    iterator end()
    {
        return _point.end();
    }

    Vec& GetStore()
    {
        return _point;
    }

    const Vec& GetStore() const
    {
        return _point;
    }

    std::vector<Point> _point;
};

      

you can use it like below.

PointCloud p;
p._point.push_back(new Point{ 1.0, 2.0 });
p._point.push_back(new Point{ 3.0, 2.0 });
p._point.push_back(new Point{ 5.0, 2.0 });
p._point.push_back(new Point{ 4.0, 2.0 });


LoopThrough(p._point, [](Point* p) { std::cout << p->x << "\t" << p->y << "\n";  });

      

0


source







All Articles