-1

This is for a school project. I have a class called buddy and a linked list buddyList. While debugging, I encountered some nullptr exceptions that I think I fixed correctly, but last I tried to debug, I got this exception. What can I do to fix this? Keep in mind some of this code was provided via the instructor and to goal of the project was to add specific methods to the existing classes. Thanks!

//buddy.h

#pragma once
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

class buddy {
friend class buddyList;

public:
    buddy() {
        first = last = phone = ""; next = NULL;
        cout << "Buddy allocated\n";
    }

~buddy() { 
    if (next != NULL) {
        delete next;
        cout << "Buddy " << first << " " << last << " deallocated!\n";
    }
}

void set(string fname, string lname, string tele) {
    first = fname;
    last = lname;
    phone = tele;
}
void print() {
    cout << left << setw(15) << last << setw(15) << first << "  " << phone << endl;
}
private:
    string first;
    string last;
    string phone;
    buddy * next;   
};

//buddyList.h

#pragma once
#include <string>
#include "buddy.h"
using namespace std;

class buddyList {
public:
    buddyList();
    ~buddyList();
    void add(string fname, string lname, string phone);
    void print();
    int drop(string fname, string lname);
    void sort();
    void read();
private:
    buddy * head;
    buddy* search(string fname, string lname);
    buddy* maxByName();
    void remove(buddy * r);
    void add(buddy * n);
};

//buddyList.cpp

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include "buddyList.h"



//------------------------------------------------------------------
//                    constructor and destructor
//------------------------------------------------------------------
buddyList::buddyList() {
    head = NULL;
    cout << "List allocated!\n";
}

buddyList::~buddyList() {
    if (head != NULL)
        delete head;
    cout << "List destroyed!\n";
}

//------------------------------------------------------------------
//                            add
//------------------------------------------------------------------
void buddyList::add(string fname, string lname, string phone) {
    buddy * b = new buddy;
    b->first = fname;
    b->last = lname;
    b->phone = phone;
    add(b);
}

void buddyList::add(buddy * p) {
    p->next = head;//Error here
    head = p;
}

  //------------------------------------------------------------------
  //                            print
  //------------------------------------------------------------------
void buddyList::print() {
    cout << "\nBuddy List: =========================================\n";
    buddy * p = head;
    while (p != NULL) {
        p->print();
        p = p->next;
    }
    cout << "======================================================\n\n";
    delete p;
    p = NULL;
} 
//------------------------------------------------------------------
//                            search
//------------------------------------------------------------------
buddy* buddyList::search(string fname, string lname) {
    buddy * p = head;
    while (p != NULL) {
        if (p->first == fname && p->last == lname)
            return p;
        p = p->next;
    }
    delete p;
    p = NULL;
    return NULL;
}
//------------------------------------------------------------------
//                            read
//------------------------------------------------------------------
void buddyList::read() {
    ifstream f;
    f.open("buddyList.txt");
    if (f.fail()) {
        cout << "Error opening input file!\n";
        return;
    }

    string fname, lname, tele;
    while (!f.eof()) {
        f >> fname >> lname >> tele;
        add(fname, lname, tele);
    }
    f.close();
}
//------------------------------------------------------------------
//                            maxByName
//------------------------------------------------------------------
buddy* buddyList::maxByName() {
    buddy * p = head;
    if (p == NULL)
        return NULL;
    buddy * q = p->next;
    while (q != NULL) {
        if (p->last > q->last) {
            p = q;
        }
        else if (p->last == q->last)
            if (p->first > q->first)
                p = q;
        q = q->next;
    }
    return p;
}
//------------------------------------------------------------------
//                            remove
//------------------------------------------------------------------
void buddyList::remove(buddy * r) {
    if (head == NULL)
        return;
    if (r == NULL)
        return;
    if (r == head)
        head = head->next;
    else {
        buddy * b4 = head;
        while (b4->next != r && b4 != NULL) {
            b4 = b4->next;
        }
        if (b4 == NULL)
            return;
        b4->next = r->next;
    }
    r->next = NULL;
    delete r;
    r = NULL;
}
//------------------------------------------------------------------
//                            drop
//------------------------------------------------------------------
int buddyList::drop(string fname, string lname) {
    buddy * p = search(fname, lname);
    if (p == NULL)
        return -1;
    else {
        remove(p);
        return 0;
    }
}
//------------------------------------------------------------------
//                            sort
//------------------------------------------------------------------
void buddyList::sort() {
    buddyList tempList;
    buddy * p = head; 
    while (p != NULL) {
    buddy * q = maxByName();
    remove(q);
    tempList.add(q);
}
delete p;
head = tempList.head;
tempList.head = NULL;

}

Hugh Lindsay
  • 9
  • 1
  • 3
  • 2
    Please provide a [mcve] that illustrates the problem – Phil M Dec 13 '18 at 01:43
  • 2
    Have you stepped through with a debugger to see which call triggers the exception? – Phil M Dec 13 '18 at 01:44
  • 1
    Perchance, in your linked list *circular* (intentionally or otherwise) ? If so, that destructor is a recipe for a call-stack overflow (and a boatload of undefined behavior after the first time around, just to rub salt in the wound). – WhozCraig Dec 13 '18 at 01:46
  • 2
    Only recursive element in this I can see is the destructor chain – Phil M Dec 13 '18 at 01:46
  • You should not be deleting b in add. That leads to undefined behavior, as now you've a node added whose had the memory yanked out from under it, and other new nodes may end up using the same memory – Phil M Dec 13 '18 at 01:52
  • 1
    It is the job of buddyList to clean up the list, buddy cannot do it correctly. A trivial way to blow the stack is to make the list too large. – Hans Passant Dec 13 '18 at 01:53
  • The project assignment said to add a destructor to buddy. After removing the statements deleting 'b' in the method add(), a nullptr exception was thrown inside of the second add() method in buddyList.cpp – Hugh Lindsay Dec 13 '18 at 01:59
  • Also, I don't know what 'stepping through' with a debugger is – Hugh Lindsay Dec 13 '18 at 02:00
  • When running the program, it seems the exception(s) are being thrown when the sort function is called. – Hugh Lindsay Dec 13 '18 at 02:08

1 Answers1

2

A stack overflow is commonly caused by recursion gone mad. By that, I mean you're doing something recursive such as delete-ing the next (in a list) element in the destructor of the current element but, for some reason, the integrity of the list is compromised.

Since your buddy class only contains information about an element in the list, you should probably be looking at the list code itself. This is, rather tantalisingly, indicated in the line:

friend class buddyList;

There's nothing inherently wrong with a destructor for an item first cleaning up all items it "owns" (such as the rest of the list) but you have to do it properly. From the code you've given, it looks okay, but everything is predicated on the fact that buddyList is working as expected.


And, in fact, now that you've added some buddyList code, that appears to be the exact problem. Looking at your code for adding an item:

buddy * b = new buddy;
b->first = fname;
b->last = lname;
b->phone = phone;
add(b);
delete b;          // Hmm!!

You have a serious problem here. You allocate a new buddy, add a pointer to it into the list, then free the memory that it refers to. That's not going to end well if, for example, that memory gets reused for something else (like another node in the list) while still being referenced by the list.


So, here's my advice, such as it is.

  1. Separate the responsibilities correctly. No given node should ever have to concern itself with the other nodes in the list. The list itself should be responsible for cleaning itself up.

  2. Don't use recursion for cleanup. The ideal use case for recursion is where the problem-space is reduced quickly. For example, in a binary search, you remove half the remaining problem space at each level of recursion. With a list of a million nodes, removing one node per level, you're almost certainly going to overflow your stack with a million separate frames.

  3. If you're not using C++ smart pointers(a), learn how to follow the flow of ownership and don't "give" an object to some other object then immediately make it unusable :-)


For example, here's an implementation that, while still using raw pointers, addresses the first two points above:

#include <iostream>
#include <memory>

class Node {
public:
    Node(const std::string &str): m_str(str), m_next(nullptr) {}

private:
    friend class NodeList;
    std::string m_str;
    Node *m_next;
};

class NodeList {
public:
    NodeList(): m_head(nullptr), m_tail(nullptr) {};
    ~NodeList() { clear(); }

    void print() {
        Node *node = m_head;
        std::cout << "List:";
        while (node != nullptr) {
            std::cout << " " << node->m_str;
            node = node->m_next;
        }
        std::cout << "\n";
    }

    void add(const std::string &str) {
        auto newNode = new Node(str);
        if (m_head == nullptr) {
            m_head = m_tail = newNode;
        } else {
            m_tail->m_next = newNode;
            m_tail = newNode;
        }
    }

    // List is responsible for cleaning itself up, and
    // it does it iteratively to mimimise chance of
    // stack blowout.

    void clear() {
        while (m_head != nullptr) {
            Node *save = m_head->m_next;
            delete m_head;
            m_head = save;
        }
        m_tail = nullptr;
    }

private:
    Node *m_head, *m_tail;
};

int main() {
    NodeList list;
    list.print();
    list.add("paxdiablo"); list.add("george"); list.add("bob");
    list.print();
    list.clear();
    list.print();
}

As expected, the output is:

List:
List: paxdiablo george bob
List:

(a) There are very few cases nowadays where you should ever use the new or delete keywords. They're okay if you totally understand and control the ownership of an object but, if there's any doubt, modern C++ provides smart pointers for making it a lot easier to manage.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Ok, I just edited the post to include the other files. Thanks! – Hugh Lindsay Dec 13 '18 at 01:52
  • Ok, I've removed the delete b; from the code and now it is throwing a nullptr in the second add() method. I also checked, and I don't think I'm using recursion anywhere. Unless I just can't see it. Thanks – Hugh Lindsay Dec 13 '18 at 02:06
  • I am pretty new to c++ and I don't know anything about smart pointers (and I'm not sure if there are permitted for this course), could you possibly, explain what they can do for you? Thanks a million – Hugh Lindsay Dec 13 '18 at 02:16
  • @HughLindsay Smart Pointers help you automate management of and broadcast [ownership](https://stackoverflow.com/questions/49024982/what-is-ownership-of-resources-or-pointers) of a resource. When you are given a raw pointer you have to make assumptions about who owns it and if the owner is you, you may have to explicitly manage it. With a smart pointer the ownership is implied by the smart pointer and the smart pointer will perform some of the most important management tasks for you. – user4581301 Dec 13 '18 at 02:55
  • 1
    There is a good write up, even if the terminology is a bit out of date, to be found in [What is a smart pointer and when should I use one?](https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one) – user4581301 Dec 13 '18 at 02:55
  • Thank you for all your suggestions and explanations paxdiblo! You have been very helpful. – Hugh Lindsay Dec 13 '18 at 03:07
  • Thanks, user4851301, I'll look into those for future projects. – Hugh Lindsay Dec 13 '18 at 03:07