1

Long story short: I made a Template Double-Linked List, and putting lists inside of other lists causes issues. This question has been solved.

I'm testing Double-Linked-Lists as templates, and I'm running into an issue where placing a linked list inside of another linked list gives the following error:

(Microsoft Visual Studio 2010 Express)

Windows has triggered a breakpoint in linkListTesting.exe. This may be due to a corruption of the heap, which indicates a bug in linkListTesting.exe or any of the DLLs it has loaded. This may also be due to the user pressing F12 while linkListTesting.exe has focus. The output window may have more diagnostic information.

Searching for this particular error yielded results along the lines of, "make sure you don't use uninitialized variables," -in this scenario, when I tell a list to push_back / push_front, etc, it should initialize the list for me. (Not pressing F12, so that scenario's out.)

Here's my main cpp->

#include "vcLLMK2.h"

int main(int argc, char *argv[])
{
    vcList<vcList<int>> a;
    vcList<int> b;
    b.push_back(1);
    a.push_back(b);
    //ERROR HAPPENS HERE APPARENTLY
    return 0;
}

Pushing back (or front, tried that too) a new entry (in this case, '1') into 'b' works great. No crashes, no silliness. Pushing 'b' into 'a' causes the application to spit out the aforementioned error, trigger a breakpoint, and die.

I've recently started playing around with SDL, and intend on using a list as a container for 16 layers, which are basically lists of sprites/tiles/etc to render. The mode of thought is that the program will iterate through each entry in the layer-containing list, then render each object in the contained layer before moving on to the next.

"Why not just use std::list?" -Because that's cheating.

Although it's pretty likely that those who answer in this thread will be able to point out each of the glaring flaws in my list's structure and implementation that I'm blissfully unaware of, what I'm most concerned about is what-in-particular is causing this peculiar error and how to remedy it. That said, my question for you folks is what is wrong with the linked-list setup that I've built that causes the above cpp to error at runtime?

The Linked List structure below is the culmination of reading the following pages:
http://www.codeproject.com/Articles/26631/Using-C-templates-for-functions-and-operators
http://www.cprogramming.com/tutorial/lesson15.html
http://geekswithblogs.net/MarkPearl/archive/2010/02/20/linked-lists-in-c.aspx
Turning it into a template is mostly the result of creative interpretation of this page:
http://www.cplusplus.com/doc/tutorial/templates/

Here's my Link-List template class, with lots of comments->

#pragma once
#ifndef vcLLMK2_H_INCLUDED
#define vcLLMK2_H_INCLUDED
#define NULL 0

//THIS IS ALL OUR NODE SHOULD EVER NEED
template <class T>
struct vcListNode
{
    public:
    T data;
    vcListNode<T> *next;
    vcListNode<T> *prev;
};

template <class T>
struct vcList
{
    public:
    vcListNode<T> *root;//the list's "start"
    vcListNode<T> *end;//the list's "end"
    vcListNode<T> *cur;//used to bounce around amongst entries

    int size();//returns number of non-null entries
    int count;//because keeping track of these is easier than iterating through every time

    void setAt(T,int);//sets data in a node
    void push_front(T);//inserts a new node at the beginning of the list
    void push_back(T);//same thing but back of list
    void removeAt(int);//kills a node at an arbitrary spot in the list
    void clear();//deletes all of the nodes in the list
    T getAt(int);//returns data from a node in the list
    vcList<T>& operator = (const vcList<T>&);//used for setting list a's contents to list b
    //EDIT COPYCONSTRUCTOR
    vcList<T>(const vcList<T>&);//copyconstructor

    vcList();// : root(NULL),end(root),cur(NULL),count(0){};
    ~vcList();
};

//constructor - sets everything to null and count to zero on init
template <class T>
vcList<T>::vcList()
{
    root=NULL;
    cur=NULL;
    end=NULL;
    count=0;
}

//destructor - deletes all nodes
template <class T>
vcList<T>::~vcList()
{
    clear();
}

//deletes all nodes from root to end
template <class T>
void vcList<T>::clear()
{
    if(root==NULL)//assume list has nothing in it, abort
    return;

    //set current targeted entry to root
    cur = root;
    //as long as we have somewhere to go...
    while(cur->next!=NULL)
    {
        //go there
        cur=cur->next;
        //and destroy where we were
        delete cur->prev;
    }
    //then destroy ourselves because nihilism is good for memory
    delete cur;
}

//used to set the contents of a list equal to those of another
template <class T>
vcList<T>& vcList<T>::operator= (const vcList<T> &fays)
{
    //for starters, clear ourselves of any unwanted garbagedata
    clear();

    //check the import list's root entry
    cur = fays.root;

    //if the list containing the values we are importing is empty, we're done, move along
    if(cur==NULL)
    return *this;

    //otherwise, make a new node - it's our new root
    vcListNode<T> *newEntry = new vcListNode<T>;
    newEntry->data = fays.root->data;
    newEntry->prev = NULL;
    newEntry->next = NULL;
    //set root/end to the new entry
    root = newEntry;
    end = newEntry;
    //update our count
    count=1;

    //fire through the import-list's entries
    //cur starts at root
    while(cur->next!=NULL)//(count<fays.count)
    {
        //move to next entry
        cur=cur->next;
        //add it to our list, push_back should update the location of 'end' for us
        push_back(cur->data);
    }
    //we should be done here, so go ahead and return everything
    return *this;
}

//this is mostly for convenience
template <class T>
int vcList<T>::size()
{
    return count;
}

//adds a new entry to the front of our linked list
template <class T> 
void vcList<T>::push_front(T info)
{
    //eat some memory
    vcListNode<T> *newEntry;
    newEntry = new vcListNode<T>;

    //set our memory and all that neat stuff
    newEntry->data = info;
    newEntry->next = root;
    newEntry->prev = NULL;

    //if our linked list is not empty
    if(root!=NULL)
        //set root's previous link to point at our new entry
        root->prev = newEntry;

    //if our linked list does not have an assigned end yet
    if(end==NULL)
        //assume it's empty and set end to our entry
        end=newEntry;

    //update the position of our root in the list, the beginning now begins at the beginning again
    root = newEntry;

    //and since we added something to the list, we should probably update our count of things in the list
    count++;
}

//this finds an element in the pointer-chain and sets its information
template <class T> 
void vcList<T>::setAt(T info,int index)
{
    //set our target to the root
    cur=root;

    //run through the list's entries until we're where we're supposed to be
    while(index>0)
    {
        cur=cur->next;
        index--;
    }

    //set the data in the cell/node/whatever to our input
    cur->data=info;
}

//returns the data contained in a node at a position in the list
template <class T> 
T vcList<T>::getAt(int meBro)
{
    //set target to root
    cur=root;
    //progress through the list
    while(meBro>0)
    {
        cur=cur->next;
        meBro--;
    }
    //dig the data out of the entry and return it
    return cur->data;
}

//adds an element-containing node to the end of the list-chain
template <class T> 
void vcList<T>::push_back(T info)
{
    //if our list already has entries, end shouldn't be null
    if(end!=NULL)
    //so just target our end slot
    cur=end;

    //if our list is empty, however
    else
        //target the root instead
        cur=root;

    //create our new node, put stuff in it, etc
    vcListNode<T> *newEntry;
    newEntry = new vcListNode<T>;
    newEntry->data = info;
    //we're adding to the END of the list so make next null
    newEntry->next = NULL;

    //if cur is NOT null, then theoretically we're pointed at the end of the list
    if(cur!=NULL)
        //set our new entry's previous pointer to the end
        newEntry->prev = cur;

    //cur IS null, which means the list is empty
    else
        //set our entry's previous pointer to be null, duh
        newEntry->prev = NULL;

    //if the end of our list exists
    if(end!=NULL)
    {
        //set the next entry in the list to point at our new entry
        end->next = newEntry;
        //then set end to target the new entry
        end=newEntry;
    }
    //and if our list does not have an end yet for some reason (read as: it's empty)
    else
    {
        //set the root to our new entry
        root = newEntry;
        //set the end to our new entry as well, since there's only one entry in the list
        end = newEntry;
    }

    //update count of number of objects in list
    count++;
}

//this deletes/removes/destroys/obliterates a node at a location in the list
template <class T> 
void vcList<T>::removeAt(int index)
{
    //for starters - is what we're trying to kill even here?
    if(index>=count)
        //NOPE GET OUT
        return;

    //later on it might speed things up to check whether the distance from end or root is shorter
    //for now, just start at the beginning

    //target the root
    cur=root;

    //move through the list to the specified entry
    while(index>0)
    {
        index--;
        cur=cur->next;
    }

    //if the previous entry exists
    if(cur->prev!=NULL)
        //point its next at the entry after this one
        cur->prev->next=cur->next;

    //if the previous entry is NULL, it means we're at the root
    //so tell root to scoot forward one entry
    //if there's a forward to scoot to
    else if(cur->next != NULL)
    root = cur->next;

    //if the next entry exists
    if(cur->next!=NULL)
        //set the next entry's previous pointer to point at the entry before the targeted one
        cur->next->prev=cur->prev;

    //if the next entry does not exist, we must be at the end of the list
    //so tell the end of the list to scoot back one slot
    //if there's a back-one-slot to go to
    else if(cur->prev!=NULL)
    end = cur->prev;

    //remove the entry at the targeted location
    delete cur;

    //decrement our count
    count--;
}
//EDIT -> Copy Constructor
//copy constructor, similar as suggested to the operator=
template <class T>
vcList<T>::vcList(const vcList<T>& fays)
{
    //might not be completely necessary, but we're not hurting anything by making sure
    clear();

//check the import list's root entry
cur = fays.root;

    //if the list containing the values we are importing is empty, we're done, move along
    if(cur==NULL)
    return;//just return, constructors don't get return types

    //otherwise, make a new node - it's our new root
    vcListNode<T> *newEntry = new vcListNode<T>;
    newEntry->data = fays.root->data;
    newEntry->prev = NULL;
    newEntry->next = NULL;
    //set root/end to the new entry
    root = newEntry;
    end = newEntry;
    //update our count
    count=1;

    //fire through the import-list's entries
    //cur starts at root
    while(cur->next!=NULL)//(count<fays.count)
    {
        //move to next entry
        cur=cur->next;
        //add it to our list, push_back should update the location of 'end' for us
        push_back(cur->data);
    }
    //we should be done here, so go ahead and return everything
    //return *this;
}
#endif
trincot
  • 317,000
  • 35
  • 244
  • 286
Viv
  • 11
  • 2
  • 2
    You've disobeyed [The Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). You need to define a custom copy constructor, or you need to disable the implicitly generated one. (by making it private in C++03, or by deleting it in C++11) – Benjamin Lindley Oct 30 '12 at 22:20
  • Of course, if you disable the copy constructor, you will almost certainly run into a host of other problems. So the proper solution is most likely a correct copy constructor implementation. – Benjamin Lindley Oct 30 '12 at 22:28
  • Learning about copy constructors now. Thanks for the push in the right direction. – Viv Oct 30 '12 at 22:37
  • If you're having trouble with the implementation, take a look at your assignment operator(assuming it's correct). Your copy constructor should be much the same as that, only you don't have to worry about cleaning up the previous contents, since there is nothing to clean up (due to this being the initialization of a new object). – Benjamin Lindley Oct 30 '12 at 22:43
  • Definitely solved the issue. Thanks again. – Viv Oct 30 '12 at 23:16

1 Answers1

1

I think you should take a look in one of the most important concepts in C++, that is pass by reference. The push_back method as an argument named info, that is a copy of the object you are using to call the method. Since you didn't explicit told the compiler how to create an copy of the vsList object, it's going to create a default copy contructor but it's not going to work as intended.

So the problem on your code really happens when the destructor of the object that is stored in the object "a" is called. Since it has no valid pointers, it's going to crash. You can solve your problems in two ways, changing the push_back (and all other methods) to receive T by "const reference" instead of by value or implementing a copy constructor.

Hugo Corrá
  • 14,546
  • 3
  • 27
  • 39
  • That actually helps explain a lot of weirdness I've encountered while testing these. – Viv Oct 30 '12 at 23:39
  • Changing the methods to take references won't fix the problem (though it may improve performance). He still has to copy the objects into the container, as happens when he creates a new node. – Benjamin Lindley Oct 30 '12 at 23:40
  • Nevermind that. The way she's implemented it, she uses default construction, then assignment. But it's still a bad idea to leave the implicitly generated copy constructor in place, because some other piece of code is going to try to copy it. – Benjamin Lindley Oct 30 '12 at 23:53