views:

72

answers:

2

I'm still working on my Field class, and tried to improve my piss-poor insertion/erase performance.

However, the new function works once, then breaks catastrophically when I use it a second time.

This is the code:

template <class T>
T *Field<T>::insert(const T *pPos, const T& data)
{
    // Special case:  field is empty. insert should still succeed.
    // Special case: Pointing to one past the end. insert should still succeed
    if( empty() || pPos == last() )
    {
        this->push_back(data);
        return (this->last() - 1);
    }

    /* Explanation:  Find cell before which to insert new value. Push_back new
      new value, then keep swapping cells until reaching *pPos and swapping it
      with data. The while fails, we exit, insert successful. */
    T *p = ( std::find( this->first(), this->last(), *pPos ));
    if( p != last() )
    {
        this->push_back(data);

        T *right = (this->last() - 1);
        T *left  = (this->last() - 2);
        while( *pPos != data )
            std::iter_swap( left--, right-- ); 

    // pPos *has* to be the destination of new value, so we simply return param.
        return const_cast<T*>(pPos);
    }
    else
        throw std::range_error("Not found");
}

Calling code from main

// Field already has push_back()ed values 10, 20, 30.
field->insert( &(*field)[2], 25 ); // field is a ptr (no reason, just bad choice)

Produces this output when printed on the console.

Field: 10 20 30    // Original Field
25                 // Function return value
Field: 10 20 25 30 // Correct insertion.

New calling code from main

// Field already has push_back()ed values 10, 20, 30
field->insert( &(*field)[2], 25 );
field->insert( &(*field)[3], 35 );

Produces this output when printed on the console.

Field: 10 20 30
25
35
-4.2201...e+37, 10, 15, 20, 30

Windows has triggered a breakpoint in Pg_1.exe. 
This may be due to a corruption in the heap (oh shit).

No symbols are loaded for any call stack frame. 
The source code cannot be displayed.

The console then proceeds to never shutdown again until I close VSC++08 itself.

What? Why? How? What is my code doing!?

Additional Info

The Field has a size of three before the push, and a capacity of four. After two insertions, the Field is correctly increased to have a capacity of 8 (doubled), and stores five elements.

It doesn't matter where I insert my second element with insert(), it will fail the exact same way. Same output, even same number (I think) at the first cell.

Additional Code

Push_Back()

Note: This code was not changed during my refactoring. This function has always worked, so I highly doubt that this will be the problem-cause.

/* FieldImpl takes care of memory management. it stores the values v_, vused_,
  and vsize_. Cells in the Field are only constructed when needed through a 
  placement new that is done through a helper function. */
template <class T>
void Field<T>::push_back(const T& data)
{
    if( impl_.vsize_ == impl_.vused_ )
    {
        Field temp( (impl_.vsize_ == 0) ? 1 
                                        : (impl_.vsize_ * 2) );

        while( temp.impl_.vused_ != this->impl_.vused_ )
            temp.push_back( this->impl_.v_[temp.size()] );

        temp.push_back(data);
        impl_.Swap(temp.impl_);
    }
    else
    {
// T *last()  const { return &impl_.v_[impl_.vused_]; }
// Returns pointer to one past the last constructed block.
// variant:     T *last()  const { return impl_.v_; }
        Helpers::construct( last(), data );
        ++impl_.vused_;
    }
}
+1  A: 
// ...
if( p != last() )
{
    this->push_back(data);

After this line pPos may not be a valid pointer anymore.

The console then proceeds to never shutdown again until I close VSC++08 itself.

Tried clicking the Stop button in the debugger?

ybungalobill
pPos may or may not be valid depending on whether a reallocation occured.
flownt
@flownt: was it meant to contradict somehow what I said?
ybungalobill
Thanks, I have used the debugger to retrace this as well. Can you tell me why this is, or what I can do about it? And yes, I do click the Stop button. Doesn't close.
SoulBeaver
@SoulBeaver: @flownt answered why: push_back reallocates the memory and now your array is at another address while pPos points to an item of the old array, which is already freed. To solve this convert pPos to an index before you do the push_back.
ybungalobill
@SoulBeaver: Also why not use std::vector to solve your problems? If you add some functionality you may inherit from it.
ybungalobill
@ybungalobill: Thanks! I've included your solution in an answer I posted, but credit goes to you two. The code works now, and yes, I could have inherited from a vector. Good practice though.
SoulBeaver
A: 

From the Debugger, and from ybungalobill, it is possible to see that pPos is invalidated after a special case in the

if( p != last()
{
    this->push_back(data);

part of the code. If the array is resized, the pointer is invalidated. To bridge this, I simply stored const T pos = *pPos before the push and therefore removed the use of the *pPos pointer after a push.

Updated code:

const T pos = *pPos;
T *p = ( std::find( this->first(), this->last(), pos ) );
if( p != last() )
{
    this->push_back(data);
    p = ( std::find( this->first(), this->last(), pos ) );

    T *right = (this->last() - 1);
    T *left  = (this->last() - 2);
    while( *p != data )
        std::iter_swap( left--, right-- ); 

    return const_cast<T*>(p);
}
SoulBeaver
Btw, what your code is intended to do. What if pos appears twice in the array? Your code will insert before the first appearance of the value pointed by pPos, not *at* pPos.
ybungalobill
@ybungalobill: Absolutely right. But that wasn't the purpose of the question. I will have to consider that regardless.
SoulBeaver