Why use iterators instead of array indices?

前端 未结 27 1881
萌比男神i
萌比男神i 2020-11-22 15:45

Take the following two lines of code:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

And this:

for (som         


        
相关标签:
27条回答
  • 2020-11-22 16:10

    Even better than "telling the CPU what to do" (imperative) is "telling the libraries what you want" (functional).

    So instead of using loops you should learn the algorithms present in stl.

    0 讨论(0)
  • 2020-11-22 16:11

    Both the implementations are correct, but I would prefer the 'for' loop. As we have decided to use a Vector and not any other container, using indexes would be the best option. Using iterators with Vectors would lose the very benefit of having the objects in continuous memory blocks which help ease in their access.

    0 讨论(0)
  • 2020-11-22 16:11

    I felt that none of the answers here explain why I like iterators as a general concept over indexing into containers. Note that most of my experience using iterators doesn't actually come from C++ but from higher-level programming languages like Python.

    The iterator interface imposes fewer requirements on consumers of your function, which allows consumers to do more with it.

    If all you need is to be able to forward-iterate, the developer isn't limited to using indexable containers - they can use any class implementing operator++(T&), operator*(T) and operator!=(const &T, const &T).

    #include <iostream>
    template <class InputIterator>
    void printAll(InputIterator& begin, InputIterator& end)
    {
        for (auto current = begin; current != end; ++current) {
            std::cout << *current << "\n";
        }
    }
    
    // elsewhere...
    
    printAll(myVector.begin(), myVector.end());
    

    Your algorithm works for the case you need it - iterating over a vector - but it can also be useful for applications you don't necessarily anticipate:

    #include <random>
    
    class RandomIterator
    {
    private:
        std::mt19937 random;
        std::uint_fast32_t current;
        std::uint_fast32_t floor;
        std::uint_fast32_t ceil;
    
    public:
        RandomIterator(
            std::uint_fast32_t floor = 0,
            std::uint_fast32_t ceil = UINT_FAST32_MAX,
            std::uint_fast32_t seed = std::mt19937::default_seed
        ) :
            floor(floor),
            ceil(ceil)
        {
            random.seed(seed);
            ++(*this);
        }
    
        RandomIterator& operator++()
        {
            current = floor + (random() % (ceil - floor));
        }
    
        std::uint_fast32_t operator*() const
        {
            return current;
        }
    
        bool operator!=(const RandomIterator &that) const
        {
            return current != that.current;
        }
    };
    
    int main()
    {
        // roll a 1d6 until we get a 6 and print the results
        RandomIterator firstRandom(1, 7, std::random_device()());
        RandomIterator secondRandom(6, 7);
        printAll(firstRandom, secondRandom);
    
        return 0;
    }
    

    Attempting to implement a square-brackets operator which does something similar to this iterator would be contrived, while the iterator implementation is relatively simple. The square-brackets operator also makes implications about the capabilities of your class - that you can index to any arbitrary point - which may be difficult or inefficient to implement.

    Iterators also lend themselves to decoration. People can write iterators which take an iterator in their constructor and extend its functionality:

    template<class InputIterator, typename T>
    class FilterIterator
    {
    private:
        InputIterator internalIterator;
    
    public:
        FilterIterator(const InputIterator &iterator):
            internalIterator(iterator)
        {
        }
    
        virtual bool condition(T) = 0;
    
        FilterIterator<InputIterator, T>& operator++()
        {
            do {
                ++(internalIterator);
            } while (!condition(*internalIterator));
    
            return *this;
        }
    
        T operator*()
        {
            // Needed for the first result
            if (!condition(*internalIterator))
                ++(*this);
            return *internalIterator;
        }
    
        virtual bool operator!=(const FilterIterator& that) const
        {
            return internalIterator != that.internalIterator;
        }
    };
    
    template <class InputIterator>
    class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
    {
    public:
        EvenIterator(const InputIterator &internalIterator) :
            FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
        {
        }
    
        bool condition(std::uint_fast32_t n)
        {
            return !(n % 2);
        }
    };
    
    
    int main()
    {
        // Rolls a d20 until a 20 is rolled and discards odd rolls
        EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
        EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
        printAll(firstRandom, secondRandom);
    
        return 0;
    }
    

    While these toys might seem mundane, it's not difficult to imagine using iterators and iterator decorators to do powerful things with a simple interface - decorating a forward-only iterator of database results with an iterator which constructs a model object from a single result, for example. These patterns enable memory-efficient iteration of infinite sets and, with a filter like the one I wrote above, potentially lazy evaluation of results.

    Part of the power of C++ templates is your iterator interface, when applied to the likes of fixed-length C arrays, decays to simple and efficient pointer arithmetic, making it a truly zero-cost abstraction.

    0 讨论(0)
  • 2020-11-22 16:12

    STL iterators are mostly there so that the STL algorithms like sort can be container independent.

    If you just want to loop over all the entries in a vector just use the index loop style.

    It is less typing and easier to parse for most humans. It would be nice if C++ had a simple foreach loop without going overboard with template magic.

    for( size_t i = 0; i < some_vector.size(); ++i )
    {
       T& rT = some_vector[i];
       // now do something with rT
    }
    '
    
    0 讨论(0)
  • 2020-11-22 16:15

    Separation of Concerns

    It's very nice to separate the iteration code from the 'core' concern of the loop. It's almost a design decision.

    Indeed, iterating by index ties you to the implementation of the container. Asking the container for a begin and end iterator, enables the loop code for use with other container types.

    Also, in the std::for_each way, you TELL the collection what to do, instead of ASKing it something about its internals

    The 0x standard is going to introduce closures, which will make this approach much more easy to use - have a look at the expressive power of e.g. Ruby's [1..6].each { |i| print i; }...

    Performance

    But maybe a much overseen issue is that, using the for_each approach yields an opportunity to have the iteration parallelized - the intel threading blocks can distribute the code block over the number of processors in the system!

    Note: after discovering the algorithms library, and especially foreach, I went through two or three months of writing ridiculously small 'helper' operator structs which will drive your fellow developers crazy. After this time, I went back to a pragmatic approach - small loop bodies deserve no foreach no more :)

    A must read reference on iterators is the book "Extended STL".

    The GoF have a tiny little paragraph in the end of the Iterator pattern, which talks about this brand of iteration; it's called an 'internal iterator'. Have a look here, too.

    0 讨论(0)
  • 2020-11-22 16:16

    After having learned a little more on the subject of this answer, I realize it was a bit of an oversimplification. The difference between this loop:

    for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
        some_iterator++)
    {
        //do stuff
    }
    

    And this loop:

    for (int i = 0; i < some_vector.size(); i++)
    {
        //do stuff
    }
    

    Is fairly minimal. In fact, the syntax of doing loops this way seems to be growing on me:

    while (it != end){
        //do stuff
        ++it;
    }
    

    Iterators do unlock some fairly powerful declarative features, and when combined with the STL algorithms library you can do some pretty cool things that are outside the scope of array index administrivia.

    0 讨论(0)
提交回复
热议问题