2

I'm trying to create a basic ArrayList implementation in C++ for class. The course's requirements are quite restrictive, and the data members and functions are required. However, when I run this, I get "Unable to access memory location", when it tries to delete the list pointer in deepCopy. I can't seem to figure out why. Please help. Thanks!

Edit:

I've narrowed the code down. I still want to give you enough information to be able to help, though.

Edit #2

US requested the full code, so I've added it back.

List.h

#ifndef LIST_H
#define LIST_H

/**
* This implentation of list is a pseudo ArrayList.  Since we don't guarantee that a class has a hashCode in C++
* this will only work for objects which have an id field.
*
*/
template <class T>
class List{
    private:
                                                // If you want the max size of the list (100), then you should probably create a const. Example:
                                                // const int MAX_SIZE;
                                                // T[] list = new T[MAX_SIZE];

                                                // But really, that is not much better.  This is just bad, overall.  It's like we're implementing a very poor ArrayList.

        T * list;                               // This is our array for our elements.
        int numberInList;                       // 
        int listSize;                           // Getter method will be getMaxSize.  This seems unnecessary if we're initializing it to 100...
        int nextIndex;                          // this is a number of the index for the next element.  Really, this should be inside a private class (who has the pointer to its next).
        double loadFactor;                      // We need to determine when we need to allocate additional slots so we don't run out if they try to add more than they have allocated.
        void deepCopy(const List<T> & toBeCopied ); // We need a method to perform a deepCopy.  Abstracts the implementation from each method. Private because we don't want it exposed to clients.
        bool isAboveThreshold();                // Check if the list is above the loadFactor.

    // Publicly available API
    public:
        List();
        List(int size);                         // Overloaded constructor to initialize the List to a runtime size
        List(const List<T> & toBeCopied);       // Copy constructor
        ~List();                                // Destructor -- Get rid of the dynamically allocated member.
        T next();                               // Gets the next element in the list, and increments next
        bool add( T & element );                // Adds an element to the list.  Also checks to make sure the element hasn't already been added.
        bool contains(T & element);             // Checks the list to see if the element exists in the list already.
        int getSize();                          // return the number of elements in the list.
        List<T> & List<T>::operator =( const List<T> & toBeCopied );
};

template <class T>
List<T>::List(){
    listSize = 100;
    list = new T[listSize];
    numberInList = 0;
    nextIndex = 0;                              // Initialize the next element to 0.
    loadFactor = 0.75;
};

template <class T>
List<T>::List(int size){
    list = new T[size];
    numberInList = 0;
    nextIndex = 0;                              // Initialize the next element to 0.
    listSize = size;
    loadFactor = 0.75;
};

template <class T>
List<T>::List(const List<T> & toBeCopied){
    deepCopy(toBeCopied);
};

/****
*
* We need to release the allocated heap memory.  Non-pointer members will be deallocated when they are out of scope.
*
*/
template <class T>
List<T>::~List(){
    delete [] list;
};

/**
* Return the number of elements in the list.
*/
template <class T>
int List<T>::getSize(){
    return numberInList;
}

/**
* Return the number of elements in the list.
*/
template <class T>
T List<T>::next(){
    return list[nextIndex++];
}

/**
* Check if the element is already in the list
*/
template <class T>
bool List<T>::contains( T & element){
                                                        // Now, to check if the item already exists, we could just iterate over the array, but that gives us linear execution time.
                                                        // It seems sub-optimal to work in linear time here, when it feels like we shouldn't have to, but honestly, I'm too tired
                                                        // to care at this point.
    for(int i = 0; i < (numberInList); i++){
        if(element != list[i])                          // We do this so that if the first item is not equal, we don't even bother checking the second condition.
        {
                                                        // The element isn't matched.  We have to finish iterating, though, before we can add it.
        }
        else{                                           // The element matched.  Return false.
            element = list[i];
            return true;
        }
    }
    return false;
}

/**
* The implementation for this is very bad.  But, the requirements in the homework documentation are very restrictive.
*
* Ideally, we would have a companion class named Entry which kept the reference to the element via composition.
*
* Obviously, this is a list, so we only want unique entries.
* 
* if successful, we return a true.  Else, we're returning false.
*
*/
template<class T>
bool List<T>::add( T & element ){
    // If we've exceeded the loadFactor, we want to expand our array before we add.
    if( isAboveThreshold() ){
        int newSize = listSize*2;
        List<T> tempPtr = List<T>(newSize);
        for(int i = 0; i < numberInList; i++){
            tempPtr.add(list[i]);
        }

        deepCopy(tempPtr);
    }
    if(!contains( element )){
        list[numberInList] = element;                   // if there are 4 in the list, the next empty index is 4, so this works.  We get our element, then post-increment.
        numberInList++;
        return true;
    }
    return false;

}

/**
* Deep copy mechanism
*/
template<class T>
void List<T>::deepCopy(const List<T> & toBeCopied){

    // Take care of shallow copying first.
    numberInList = toBeCopied.numberInList;
    listSize = toBeCopied.listSize;
    nextIndex = 0;                                      // We're getting a new list, so our iterator should start over.

                                                        // Now, to initialize the new list
    T *tempList = new T[listSize];

    for(int i = 0; i < toBeCopied.numberInList; i++){
        // We can do this because we're in the List class.  We have access to private members.
        tempList[i] = toBeCopied.list[i];
    }

    delete [] list;
    list = tempList;



}

/**
* boolean for if we've exceeded the loadFactor threshold.
*/
template<class T>
bool List<T>::isAboveThreshold(){
    if(numberInList != 0){
        double division = (double)numberInList/listSize;
        return (division >= loadFactor)? true : false;
    }else
        return false;
}

/***
*   Overloaded assignment operator
*/
template <class T>
List<T> & List<T>::operator =( const List<T> & assigner ){
    if(*this == &assigner)
        return *this;
    delete[] list;
    deepCopy(assigner);
    return *this;
}
#endif

Client.cpp

#include "List.h"
#include <string>
#include<iomanip>
#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;

struct Customer{
    int id;
    string name;
    string city;
    string address;
    float amount;
    Customer(){id=0; city="Default"; name="N/A"; address="N/A", amount = 0;}
    bool operator==( const Customer & assign){
        if(assign.id == id)
            return true;
        else
            return false;
    }
    bool operator!=( const Customer & assign){
        if(assign.id != id)
            return true;
        else
            return false;
    }
};

List<Customer> readCustomers();
void printCustomers();

int main(){
    cout.setf(std::ios::fixed);
    printCustomers();
    return 0;
}

// Definitions
List<Customer> readCustomers(){
    List<Customer> WebsterCommunications(50);
    ifstream custFile;
    custFile.open("Customers.csv");

    // This could be abstracted out into another method, where we took the struct, a struct name, and the inFile, and spit back a 
    // customer from the file.  But, for now, we'll just settle with the code duplication.
    if(!custFile){
        cout << "There was a problem reading the Customer File..." << endl;
        exit(99);
    }

    while(!custFile.eof()){
        Customer tempCust;
        custFile>>tempCust.id;
        if(tempCust.id == 0)
            break;
        custFile.ignore();
        getline(custFile, tempCust.name, ',');
        getline(custFile, tempCust.address, ',');
        getline(custFile, tempCust.city, ',');
        custFile>>tempCust.amount;
        custFile.ignore();
        WebsterCommunications.add(tempCust);
    }
    custFile.close();
    return WebsterCommunications;
}

void printCustomers(){
            List<Customer> customers = readCustomers();
            double addCalc = 0.0;

            cout << string( 100, '\n' );
            for(int i = 0; i < customers.getSize(); i++){
                Customer cust;
                cust = customers.next();
                cout << "id: " << cust.id << " name: " << cust.name << " address: " << cust.address << " balance: " << cust.amount << endl;
                addCalc += cust.amount;
            }
            cout.precision(2);
            cout << "average: " << (addCalc / customers.getSize()) << endl;
            int isActive = 1;
            cout << "Please enter a customer's id to find them (0 to exit):" << endl;
            while(isActive){
                cin >> isActive;
                if(!isActive){
                    return;
                }
                Customer tempCust;
                tempCust.id = isActive;
                if(customers.contains(tempCust)){
                    cout << "id: " << tempCust.id << " name: " << tempCust.name << " address: " << tempCust.address << " balance: " << tempCust.amount << endl;
                }
                else{
                    cout << "That customer is not found" << endl;
                }
                cout << "Please enter a customer's id to find them (0 to exit):" << endl;
            }
}
Kyle Richter
  • 406
  • 3
  • 18
  • 5
    Even though your assignment may require all of what's present, we don't. You need to narrow things down to the smallest piece of code you can that demonstrates the problem, then point to the exact part of the code that causes the problem to stand much chance of getting real help. Not many are likely to simply download and debug your code for you. – Jerry Coffin Feb 03 '13 at 16:59
  • 1
    Just a remark: Calling both your class member and the parameter to your copy constructor the same name (`list`) will inevitably lead to confusion. I think this is also the root of your problem. – us2012 Feb 03 '13 at 17:00
  • `delete[] list; deepCopy(list); ` looks really suspicious. – Vaughn Cato Feb 03 '13 at 17:02
  • You're right. I updated the code. Same issue, though. – Kyle Richter Feb 03 '13 at 17:20
  • Which compiler do you use for this? It doesn't compile with g++ at all, I think the way you define functions inside your class definition of `List` is incorrect or at least non-standard. edit: Never mind, your "narrowing down of the code" probably took away parts it needed to compile... – us2012 Feb 03 '13 at 17:26
  • See, I removed much of the code because Jerry told me to get down to only the important parts. The above code shouldn't compile. Would you like me to add the compilable code back? I'm using Visual Studio(It's required by the course). – Kyle Richter Feb 03 '13 at 17:29
  • I've added the full program back in. – Kyle Richter Feb 03 '13 at 17:33
  • 1
    @Kyle I took the original code from before it was edited, and that one does compile for me, but I can't reproduce the error yet. Can you put the Customers.csv on pastebin or something and tell me the steps you use to reproduce the bug? – us2012 Feb 03 '13 at 17:33
  • http://pastebin.com/FiUusxFr – Kyle Richter Feb 03 '13 at 17:38
  • @KyleRichter With that file I can't find any problems, even with valgrind. I suspect that the problem is solved with my answer below, but you are using precompiled headers in Visual C++ and somehow not rebuilding everything, so that the old code is still in your binary. – us2012 Feb 03 '13 at 17:40
  • @us2012 Yeah, I thought about that too, so I made sure Precompiled headers were turned off. – Kyle Richter Feb 03 '13 at 17:44
  • @Kyle Also, this line `List & List::operator =( const List & toBeCopied );` may work in VC++, but I don't think it's correct C++ as such. You don't need the `List::` qualifier when the declaration is inside the `class` block. Then, just to make sure you're not somehow using old code, quickly create a new VC++ project and paste the code in again to make sure everything is recompiled. – us2012 Feb 03 '13 at 17:45
  • @us2012 Good idea. But no cigar. – Kyle Richter Feb 03 '13 at 17:48
  • @Kyle Reproduced problem on VC++ and found the source, see my edit in the answer below. – us2012 Feb 03 '13 at 18:16

2 Answers2

2

In operator=, you try to delete the internal list that is currently attached to your object, but what you actually do is delete the list that you wanted to copy - inside the copy constructor, list names the parameter and not the class member.

Resolve this by giving different names to the class member and the parameter. There are well-defined rules for name shadowing in C++, but relying on them will almost always lead to confusion.


And you're still double-deleting list! Once inside your deepCopy and once inside your operator= ! You need to rethink your design and what should clean up after which resources. A quick fix is not deleting in deepCopy, but I haven't thought about how this might leak. I'll leave it to you to find a better solution.

us2012
  • 16,083
  • 3
  • 46
  • 62
  • I changed the prototypes and the definition, but the error is still occuring. I hadn't even thought of that though, when I was running through and laying out a prototype. So, thank you. :) – Kyle Richter Feb 03 '13 at 17:14
  • The problem was the copy constructor. At the point where it calls deepCopy, the list pointer has not had memory allocated for it. So, by initializing the pointer to an array of Customer structs, we eliminate the problem. – Kyle Richter Feb 03 '13 at 19:07
  • I've voted you up for all of your best practices and other advice. – Kyle Richter Feb 03 '13 at 19:08
1

The problem was the copy constructor. At the point where it calls deepCopy, the list pointer has not had memory allocated for it. So, by initializing the pointer to an array of Customer structs, we eliminate the problem.

Kyle Richter
  • 406
  • 3
  • 18
  • That may be a possible solution, but it does unnecessary allocations, no? You're allocating memory for `list`, only to instantly call `deepCopy` where you delete the memory that was just allocated, so you can point at the new memory. – us2012 Feb 03 '13 at 19:11
  • If you're interested, read about [the copy and swap idiom](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) and [the rule of three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) which will provide further ideas of how to design classes that manage resources (like yours). – us2012 Feb 03 '13 at 19:21
  • For sure, it's not efficient. But, as my version already makes quite a few optimizations and additions to the request (expanding the list if we reach a threshold for instance), I'm not really worried about it. And thanks! I'll look at those. – Kyle Richter Feb 03 '13 at 19:36