Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reversing an on-demand iterator

I have an iterator DataIterator that produces values on-demand, so the dereference operator returns a Data, and not a Data&. I thought it was an OK thing to do, until I tried to reverse the data DataIterator by wrapping it in a reverse_iterator.

DataCollection collection

std::reverse_iterator<DataIterator> rBegin(iter) //iter is a DataIterator that's part-way through the collection
std::reverse_iterator<DataIterator> rEnd(collection.cbegin());

auto Found = std::find_if(
    rBegin, 
    rEnd,
    [](const Data& candidate){
        return candidate.Value() == 0x00;
});

When I run the above code, it never finds a Data object whose value is equal to 0, even though I know one exists. When I stick a breakpoint inside the predicate, I see weird values that I would never expect to see like 0xCCCC - probably uninitialized memory. What happens is that the reverse_iterator's dereference operator looks like this (from xutility - Visual Studio 2010)

Data& operator*() const
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp); //Here's the problem - the * operator on DataIterator returns a value instead of a reference
}

The last line is where the problem is - a temporary Data gets created and reference to that data gets returned. The reference is invalid immediately.

If I change my predicate in std::find_if to take (Data candidate) instead of (const Data& candidate) then the predicate works - but I'm pretty sure I'm just getting lucky with undefined behavior there. The reference is invalid, but I'm making a copy of the data before the memory gets clobbered.

What can I do?

  1. Fix my DataIterator so that operator* returns a Data& instead of a Data? I don't really see how this is possible. The whole point of my DataIterator returning a Data instead of a Data& is because I don't have room to hold the entire uncompressed data set in memory, so I create the items that you want to look at on demand. Maybe I could hold onto the 'current' data value - but then that reference is going to become invalid the moment you increment or decrement the DataIterator. Edit one of the answers suggests a shared_ptr
  2. Write a specialization of the reverse_iterator and make its dereference operator return a value instead of a reference? This seems like a frustrating amount of work, but understandable since it's my DataIterator that's not playing nice here - not the rest of the STL.
  3. Along the same lines, maybe make a find_if that goes in reverse - probably less work than specializing reverse_iterator.
  4. Something else I haven't thought of

Is there something I can do to DataIterator that will prevent someone else from blowing half a day figuring out what's wrong when they try the same thing 6 months from now?

like image 653
Pete Baughman Avatar asked Nov 01 '22 07:11

Pete Baughman


2 Answers

Not that I'm a great fan of the idea, but if you heap allocated a Data object and then returned a ref to a shared_ptr to it, that would allow the outside world to hold onto it longer if needed, and for you to "forget" about it when you step forward.

On the other hand, implementing your own native reverse_iterator might be a bigger win. That's what I did for my own linked list since I didn't use a sentinel object like gcc does and couldn't use std::reverse_iterator. It really wasn't that difficult.

like image 114
woolstar Avatar answered Nov 15 '22 05:11

woolstar


It's because the reverse_iterator interface was designed before the existence of decltype. Today, that would be written as

auto operator*() const -> decltype(*current)
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp);
}

and in C++14, even the trailing return type won't be needed, since it can be inferred.

decltype(auto) operator*() const
{   // return designated value
    DataIterator _Tmp = current;
    return (*--_Tmp);
}
like image 45
Ben Voigt Avatar answered Nov 15 '22 05:11

Ben Voigt