0

I am writing a program that contains a singly linked list to hold a shopping list. Each node has the item name, quantity, and quantity description (i.e. dozen for eggs). Everything works find in the program except the destructor. I can't seem to find what is wrong with it though.

The driver will execute to the end where the code is return 0;, then the destructor is called and stops on the line delete current; with the message:

"Unhandled exception at 0x0FC7A9E8 (msvcr120d.dll) in Project 14.exe: 0xC0000005: Access violation reading location 0xFEEEFEE2.".

I've posted the implementation for the big three functions below. The default constructor initializes both pointers (first, last) as null and the nodeCount as 0.

I can't seem to find the problem. Any help?

List::List(const List& b)
{
    Node* newNodePtr = new Node;
    Node* nodeCopy = b.first;
    newNodePtr = nodeCopy;
    first = newNodePtr;
    last = newNodePtr;
    nodeCount++;
    nodeCopy = nodeCopy->getNext();
    while (last != b.last)
    {
        Node* newNode = new Node;
        newNode = nodeCopy;
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
        nodeCount++;
        nodeCopy = nodeCopy->getNext();
    }
}

List::~List()
{
    Node* current = first;
    while (current != nullptr)
    {
        Node* _next = current->getNext();
        delete current;
        current = _next;
    }
    first = nullptr;
    last = nullptr;
}

List& List::operator=(const List& rho)
{
    Node* current = first;
    while (current != nullptr)
    {
        Node* _next = current->getNext();
        delete current;
        current = _next;
    }
    first = nullptr;
    last = nullptr;

    Node* newNodePtr = new Node;
    Node* nodeCopy = rho.first;
    newNodePtr = nodeCopy;
    first = newNodePtr;
    last = newNodePtr;
    nodeCount++;
    nodeCopy = nodeCopy->getNext();
    while (last != rho.last)
    {
        Node* newNode = new Node;
        newNode = nodeCopy;
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
        nodeCount++;
        nodeCopy = nodeCopy->getNext();
    }
    return *this;
}

EDIT: I've also added my push_back function as it is written:

void List::push_back(Node* newNode)
{
if (first == nullptr)
{
    first = newNode;
    last = newNode;
}
else
{
    Node* currentNode = last;
    currentNode->setNext(newNode);
    last = newNode;
}
nodeCount++;
}

Alright I think I've figured in out. This code seems to work and it fits the driver provided by my professor. Below I've included the big three functions and all of the other functions they call:

List::List(const List& b)
{
    this->copyList(b);
}

List::~List()
{
    this->clearList();
}

List& List::operator=(const List& rho)
{
    this->clearList();
    this->copyList(rho);
    return *this;
}

void List::clearList()
{
    Node* current = first;
    while (current != nullptr)
    {
        current = pop_front();
        delete current;
        current = first;
    }
    first = nullptr;
    last = nullptr;
}

void List::copyList(const List& b)
{
    first = nullptr;
    last = nullptr;
    nodeCount = 0;
    Node *headNode = b.getFirst();
    while (headNode != nullptr)
    {
        string des = headNode->getDescription();
        string qNa = headNode->getQuantityName();
        int qNu = headNode->getQuantityNumber();
        Node* newNode = new Node(qNu, qNa, des);
        push_back(newNode);
        headNode = headNode->getNext();
    }
}

Node* List::pop_front()
{
    Node* saveFirst = first;
    first = first->getNext();
    nodeCount--;
    return saveFirst;
}

void List::push_back(Node* newNode)
{
    if (nodeCount == 0)
    {
        first = newNode;
        last = newNode;
    }
    else
    {
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
    }
    nodeCount++;
}
AstroCB
  • 12,337
  • 20
  • 57
  • 73
JamaicanBambi
  • 39
  • 2
  • 8
  • What about the destructor of `Node`? Can you show us that please? – David G Apr 18 '14 at 22:11
  • I was told by professor not to include a destructor for the Node class. – JamaicanBambi Apr 18 '14 at 22:12
  • This code is incredibly exception unsafe, and leaks horribly. Has your professor forbidden you to use `std::shared_ptr` too? – Mgetz Apr 18 '14 at 22:14
  • 3
    One example of a leak: `Node* newNodePtr = new Node; Node* nodeCopy = b.first; newNodePtr = nodeCopy;` leaks the memory allocated via `new`. – dyp Apr 18 '14 at 22:15
  • Do you have a function that returns a node from an existing list, given a position? How about a function that returns the number of items in the list? How about a function that adds an item to the end of the list? If yes to all three, then the copy constructor is simply a loop that gets the node data from the existing list and adds it to the end of the empty list. Hardly any, if any, pointer code is required, as these other functions would do the job. In other words, `code reuse`. If not, you will eventually see that you're writing code now that is duplicated in those other functions. – PaulMcKenzie Apr 18 '14 at 22:17
  • I would first get rid of the code duplications... there is too much of it. There can be small (helper) functions... – Arun Apr 18 '14 at 22:18
  • 1
    @Arun - Exactly, I agree. A lot of posters who have these linked list assignments and implement copy constructors do it the hard way. They don't see that all of that code they're writing is already done in the other functions. If they didn't code these other functions, then what good is the linked list they've coded, if for example, you can't get any information from it, or add items to it. – PaulMcKenzie Apr 18 '14 at 22:20
  • He hasn't said anything about std:shared_ptr, but I know we haven't gone over that. As far as the functions go, I have a 'push_back, push_front, pop_back, and pop_front' to add or remove pointers. I do have one that returns the number of pointers as well, but not one that returns a node given a position. He told us which functions to write. I've noticed the duplication with these three functions and I should probably change that. – JamaicanBambi Apr 18 '14 at 22:22
  • @user3208991 - ok. So you have basically the copy constructor coded. It is right there and you aren't seeing it. Given that these other functions you mentioned work correctly, then you're 90% done. Do you have a function (sort of like an iterator) that can go from the beginning node to the end node in some sort of loop? – PaulMcKenzie Apr 18 '14 at 22:23
  • I don't have an iterator, and to be frank, I'm not sure exactly how to code one. I'm still pretty new. – JamaicanBambi Apr 18 '14 at 22:25
  • You need to loop from the first node to the last node in the list that is sent to you. I think that's all you need to do, and just use the other functions you mentioned to do the tricky pointer work. – PaulMcKenzie Apr 18 '14 at 22:26
  • Okay. So should I avoid using allocated memory on the heap? – JamaicanBambi Apr 18 '14 at 22:28
  • @user3208991 - See my answer, and tailor your code to approximate what I wrote. You're not avoiding allocating memory, but what you are avoiding is doing that work in the copy constructor code. All of that work is taken care of by the push_back() function. That is what I mean by `code reuse`. – PaulMcKenzie Apr 18 '14 at 22:31
  • @PaulMcKenzie - I just added my code for the push_back function to the post. I didn't use any allocated memory, should I change how it is written? – JamaicanBambi Apr 18 '14 at 22:39
  • The push_back() should do the work of creating a node dynamically, sticking data inside of the node, and linking it up with the list. I would start with that first. Once you do that, then the copy constructor becomes practically trivial (if you read my answer below). I would even change push_back() so that you give it the `data` to add, not the node, for example if it is a Linked list of integers, then give push_back() the integer to add, not the Node. The push_back() should be creating the Node, and not an outside entity. – PaulMcKenzie Apr 18 '14 at 22:40
  • @user3208991 Also, look at the big picture. What if I want to use your List class in another program? Why should I be creating the Node, when all I want to do is say to your list class "here is my data, now go add it to the end of the list"? Let the List class figure out what a Node is, how to create one, etc. Don't leave that up to the client using your List class to figure out these details. – PaulMcKenzie Apr 18 '14 at 22:45
  • Make sure the `newNode` that you `push_pack` has `next` set to `nullptr` otherwise, in the destructor, this line `while (current != nullptr)` can evaluate to true and you try to delete an invalid pointer. Also make sure `last->next` is always `nullptr` –  Apr 19 '14 at 00:10
  • @user3208991 - Make sure that your push_back() function works without issues, as this decides whether your copy/assignment will work. Your copy constructor in the updated version doesn't seem to have any issues itself. Your assignment operator has a bug in that it doesn't check to see if you're copying a list to itself. In other words, `List list1; list1 = list1;` will not work correctly. The copy-swap idiom mentioned takes care of this, but in your immediate case, you need to check if `&rho` is not equal to `this` before proceeding. Otherwise, the assignment operator also looks good. – PaulMcKenzie Apr 19 '14 at 17:17
  • @user3208991 - Also, the copy/swap idiom takes care of exception safety. The issue that may not be mentioned by your professor is that `new` could throw an exception while you're copying the list, thus corrupting the list you're changing. To counter this, the copy-swap should be used, i.e. you're creating a temporary list from `rho`, and only if that temporary list is created without an exception being thrown, then `this` list is assigned the temporary list. – PaulMcKenzie Apr 19 '14 at 17:22

2 Answers2

0

It will depend at least in part on what first is pointing to when you call the destructor.

Your code is not copying the contents of the nodes. Instead it is only manipulating pointers so, as dyp pointed out, you have a leak with: Node* newNodePtr = new Node; Node* nodeCopy = b.first; newNodePtr = nodeCopy;

You might want to read up on the copy-swap idiom. What is the copy-and-swap idiom?

Community
  • 1
  • 1
user823981
  • 102
  • 6
0

This may not solve your exact issue, but if you have the functions you mentioned, then pseudo-code to a copy constructor would look like this.

List::List(const List& b)
{
   Node *headNode = b.getHeadNode();
   while (headNode != NULL)
   {
      push_back(headNode->getDataFromNode());
      headNode = headNode->getNextNode();
  }
}

So basically, that in a nutshell is the entire copy constructor. You're basically starting from the first node in the list, getting the data from that node, and calling your push_back() to add the new data. I'm assuming that push_back() does all of the tricky work of creating a node, adding the data to it, and placing it correctly at the back of the list.

Note how small, compact, and intuitive this implementation is. We know that all we need to do to create a copy of a linked list is to first make sure the list is empty (which it is for a new object), and keep adding the items from the old list to the new list. Since push_back() adds an item to a list (and has all the complexity of creating a node and linking it to the end node), we use it in a smart way to create our copy.

Note that you also need an assignment operator to go along with the copy constructor. The assignment operator in the example is simply calling clear() (if you have such a function) to remove all the nodes before proceeding.

Remember that all of this requires that your push_back() function works flawlessly. It should know how to properly handle inserting at the end of an empty and non-empty list.

Edit:

If your driver code does create the new node before the push_back (therefore push_back doesn't allocate the new node), then the alternative code can be used:

List::List(const List& b)
{
   Node *headNode = b.getHeadNode();
   while (headNode != NULL)
   {
      Node *newNode = new Node(headNode->getDataFromNode());
      push_back(newNode);
      headNode = headNode->getNextNode();
  }
}

In the alternate version, I'm assuming that the constructor for the new node can be created with the data as an argument. I personally don't like the design of having push_back() not do all of the work of creating the node, but that's another issue.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • Thanks for the help! I added my fixed code to the post if you wouldn't mind taking a look at it. The only reason I didn't dynamically allocate a new node in the `push_back` function was because the driver already did that (and we aren't supposed to change the provided driver). I made up for that by allocating a new node in the `copyList` function. However, outside of this project, I see what you are saying and I would make sure to create the node within the class code. – JamaicanBambi Apr 19 '14 at 04:45
  • @user3208991 - I updated my answer to include your current scenario of having push_back() just add the node at the end of the list. – PaulMcKenzie Apr 19 '14 at 17:06