0

I cannot tell what is wrong with my move assignment operator, here is the function. I don't think I am grabbing the data correctly, because when I run the test I get a random negative number and a "you're program has stopped working)

virtual LinkedList<T> &operator=(LinkedList<T> &&other)
{
    cout << " [x] Move *assignment* operator called. " << endl;

    // Delete our own elements
    ListNode<T> *temp1 = _front;
    while (temp1 != nullptr)
    {
        ListNode<T> *n = temp1->getNext();
        delete temp1;           
        temp1 = n;
    }
    // Grab other data for ourselves
    ListNode<T> *temp2 = other._front;
    while (temp2 != nullptr)
    {
        addElement(temp2->getValue());
        temp2 = temp2->getNext();
    }
    // Reset their pointers to nullptr

    other._front = nullptr;
    other._end = nullptr;
    other._size = 0;
    other._last_accessed_index = 0;
    other._last_accessed_node = nullptr;

    return *this;
}

Test Code- this is my teachers test code -

// Use move *assignment* operator
cout << " [x] Test #5: Move *assignment* constructor behavior" << endl;
moved1 = LinkedList<int>{ 6, 7, 8, 9, 10 };
cout << "   [x] Result:" << endl;
cout << "   [x]  Expected:\t6 7 8 9 10" << endl;
cout << "   [x]  Actual:\t\t";
for (int i = 0; i < moved1.getSize(); i++)
{
    cout << moved1.getElementAt(i) << " ";
}
cout << endl << endl;

this is my first time working with move and the move assignment operator. Thanks :)

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
sarah campolt
  • 148
  • 2
  • 13
  • What does the debugger show you when you step through the code? – Ken White Sep 14 '17 at 01:09
  • it will run through everything, but when I run my test code it breaks when trying to receive the data in the list @KenWhite – sarah campolt Sep 14 '17 at 01:11
  • Can you post the test code - or preferably a minimum subset of it that demonstrates the problem? – norlesh Sep 14 '17 at 01:12
  • If you step through the code, watching the variables, you'll see the issue. Either you're not stepping through the code, or you're not doing it correctly. Step through, line by line, and watch the values and contents of the variables. And if you have test code, you should include a [mcve] here in your post so we have it as well. – Ken White Sep 14 '17 at 01:14
  • it has been added @norlesh – sarah campolt Sep 14 '17 at 01:14
  • okay i will try to step through it again, its kind of difficult, because I have been coding in visual studio which has a debugger, but now I am working in linux on windows code which is basically just text base. Thanks though @KenWhite – sarah campolt Sep 14 '17 at 01:15
  • The move assignment operator sure looks like a regular copy assignment operator. The whole purpose of move semantics is to short circuit all the tedious copying stuff, and simply swap the state of the two objects, quickly. And, after going through the motions of copying `other`, how does the move assignment end? By nuking, from high orbit, `other`'s pointers. Sure looks like a guaranteed memory leak to me. – Sam Varshavchik Sep 14 '17 at 01:17
  • 1
    Oh, and the bug looks rather obvious. After removing `this`'s data, the `front_` pointer is not reset, and it's left pointing to `delete`d objects. Hello memory corruption! – Sam Varshavchik Sep 14 '17 at 01:20
  • what would you say the best way to copy the data is then after the comment //grab other data for oursleves then? @SamVarshavchik – sarah campolt Sep 14 '17 at 01:20
  • The key word in your comment is "copy". Wrong. This is not copy. This is a move. Nothing should be copied. The state of the two objects simply needs to be swapped. Swap the `front_` elements, of the two objects, swap everything else that needs to be swapped, etc... – Sam Varshavchik Sep 14 '17 at 01:21

2 Answers2

1

This is not a proper implementation of a move assignment operator. It looks more like a copy assignment operator (but not a good one, as it leaks memory).

A typical move assignment operator would look more like this instead:

#include <utility>

LinkedList<T>& operator=(LinkedList<T> &&other)
{
    cout << " [x] Move *assignment* operator called. " << endl;

    std::swap(_front, other._front);
    std::swap(_end, other._end);
    std::swap(_size, other._size);
    std::swap(_last_accessed_index, other._last_accessed_index);
    std::swap(_last_accessed_node, other._last_accessed_node);

    return *this;
}

A move assignment operator should not free anything. Move ownership of the source's content to the target object, and vice versa. Let the source object free the target object's previous content when the source object is destroyed after the assignment operator exits, so make sure the class also has a proper destructor implementation:

~LinkedList()
{
    // Delete our elements
    ListNode<T> *temp = _front;
    while (temp != nullptr)
    {
        ListNode<T> *n = temp->getNext();
        delete temp;           
        temp = n;
    }
}

For good measure, here is what the copy constructor, move constructor, and copy assignment operators could look like:

LinkedList() :
    _front(nullptr),
    _end(nullptr),
    _size(0),
    _last_accessed_index(0),
    _last_accessed_node(nullptr)
{
    cout << " [x] Default *constructor* called. " << endl;
}

LinkedList(const LinkedList<T> &src)
    : LinkedList()
{
    cout << " [x] Copy *constructor* called. " << endl;

    ListNode<T> *temp = src._front;
    while (temp != nullptr)
    {
        addElement(temp->getValue());
        temp = temp->getNext();
    }
}

LinkedList(LinkedList<T> &&src)
    : LinkedList()
{
    cout << " [x] Move *constructor* called. " << endl;    
    src.swap(*this);
}

LinkedList(initializer_list<T> src)
    : LinkedList()
{
    cout << " [x] Initialization *constructor* called. " << endl;

    const T *temp = src.begin();
    while (temp != src.end())
    {
        addElement(*temp);
        ++temp;
    }
}

LinkedList<T>& operator=(const LinkedList<T> &other)
{
    cout << " [x] Copy *assignment* operator called. " << endl;

    if (&other != this)
        LinkedList<T>(other).swap(*this);

    return *this;
}

LinkedList<T>& operator=(LinkedList<T> &&other)
{
    cout << " [x] Move *assignment* operator called. " << endl;
    other.swap(*this);        
    return *this;
}

void swap(LinkedList<T> &other)
{
    std::swap(_front, other._front);
    std::swap(_end, other._end);
    std::swap(_size, other._size);
    std::swap(_last_accessed_index, other._last_accessed_index);
    std::swap(_last_accessed_node, other._last_accessed_node);
}

The copy and move assignment operators can actually be merged together into a single implementation, by taking the input object by value and letting the compiler decide whether to use copy or move semantics when initializing that object, based on the context in which the operator is called:

LinkedList<T>& operator=(LinkedList<T> other)
{
    cout << " [x] *assignment* operator called. " << endl;
    swap(other);
    return *this;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks so much for everything you have added! For this exercise, I think the "move assignment operator" is just supposed to simulate the what the move assignment operator would do using "copy" methods, if that made any sense. At the moment, my code is outputing 1 2 3 4 5 when I need 10 9 8 7 6, if you could offer any clarification for this, it would be greatly appreciated. – sarah campolt Sep 14 '17 at 02:25
  • Again, thanks for all this information though, I was having trouble understanding the concepts of everything :) – sarah campolt Sep 14 '17 at 02:25
  • Implementing a move assignment operator using copy semantics is wrong. That defeats all of the benefits of move semantics. If you are going to do that, you may as will just implement a copy assignment operator by itself. The code I have given you should not be outputting `1 2 3 4 5` if it was given `6 7 8 9 10`. If you are getting `1 2 3 4 5` after move assignment, you are doing something wrong. – Remy Lebeau Sep 14 '17 at 02:30
0

It's hard to be sure without having the rest of the code, but it looks like you're not correctly clearing the list that is being assigned.

When you do:

// Delete our own elements
ListNode<T> *temp1 = _front;
while (temp1 != nullptr)
{
    ListNode<T> *n = temp1->getNext();
    delete temp1;           
    temp1 = n;
}

You are not actually removing the elements from this. Because of that, moved1 contains nodes that have been deleted, and the execution fails when you start looping on them. What you want to do is remove the nodes from the list just before deleting them.

The way to go would be:

// Remove our own elements
ListNode<T> *temp1 = _front;
while (temp1 != nullptr)
{
    ListNode<T> *n = temp1->getNext();
    pop();
    delete temp1;           
    temp1 = n;
}

And have a method like:

void pop() // removes the first element from the list
{ 
    _front = _end._front; 
    _end = _end._end; 
    --_size; 
}

Of course the definition of pop will depend on your full implementation of the class. If you are storing pointer to the object that were given to you, you should probably not delete those. However if you are using an additional wrapper such a ListNode, your pop method should delete them - although in the case of a wrapper, it's even better not to use pointers at all.

You can have a look at std::list::pop_front for more information.

Flynsee
  • 596
  • 4
  • 17
  • Thanks for your response! When I run the code as you suggested, the list has the numbers 1 2 3 4 5 6 7 8 9 10, and I want the output of 10 9 8 7 6, but right now my output is 1 2 3 4 5 – sarah campolt Sep 14 '17 at 02:19
  • Is it because removeElementAt already deletes it? In which case you don't need to delete it again in the move assignment operator. – Flynsee Sep 14 '17 at 02:24
  • Thats exactly it, I realized my mistake and updated the response! :) – sarah campolt Sep 14 '17 at 02:26