0

I'm not very good with memory management and I'm hoping someone can help explain to me why I am getting the EXC_BAD_ACCESS (code=1...) error. Xcode says that the error occurs when calling the getWord() method.

I am implementing a trie data structure and the error occurs when I try to get a word from my node. I think the problem is with my add or addPhrase method but I cant figure out whats going on. Any suggestions are appreciated.

Trie and Node Class:

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <sstream>


using namespace std;


class Node
{
private:
    string word;
    bool endOfSentence = false;
    int weight = -1;


public:

    vector<Node> children = {};

    Node() {
        this->setWord("");
    }

    Node(string s){
        this->setWord(s);
    }

    string getWord(){
        return this->word;
    }
    /*vector<Node> getChildren() {   //children private
        return this->children;
    }*/
    void setWord(string s) {
        this->word = s;
    }

    void setEOS(){
        this->endOfSentence = true;
    }

    void setWeight(int weight){
        this->weight = weight;
    }
};


class Trie
{
public:
    Node root = *new Node();

    string get(string p) {
        string s = "stub";
        return s;
    }

    void add(vector<string> phrase, int weight){
        Node current = this->root;
        vector<string> sentence = phrase;
        int w = weight;
        int found = -1;

        for (int i = 0; i < current.children.size(); i++) {
            if (phrase[0] == current.children[i].getWord()) {
                found = i;
            }
        }
        if (found >= 0) {
            current = current.children[found];
            sentence.erase(sentence.begin());
            add(sentence,w);
        }
        else {
            addPhrase(sentence,w);
        }
    }

    void addPhrase(vector<string> phrase, int weight) {
        Node current = this->root;
        for (int i = 0; i < phrase.size(); i++) {
            Node temp = *new Node(phrase[i]);
            current.children.push_back(temp);
            current = current.children[current.children.size() - 1];
            if (i == phrase.size() - 1) {
                current.setEOS();
                current.setWeight(weight);
            }
        }
    }
};

Main - just attempts to the the word from the first node.

#include "Trie.cpp"
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

int main(int argc, char* argv[]) {
    // Initialize trie up here
    Trie myTrie = *new Trie();

    // parse input lines until I find newline
    for(string line; getline(cin, line) && line.compare(""); ) {
        stringstream ss(line);
        string string_weight;
        ss >> string_weight;
        int weight = stoi(string_weight);

        // I am just going to put these words into a vector
        // you probably want to put them in your trie

        vector<string> phrase = {};
        for(string word; ss >> word;) {
            phrase.push_back(word);
        }


        myTrie.add(phrase, weight);


    }
    // parse query line
    string query;
    getline(cin, query);

    cout << myTrie.root.children[0].getWord() << endl;



    return 0;
}
Dez
  • 21
  • 6
  • Inconclusive (Need [mcve]) but you are vulnerable to Rule of Three violations. [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – user4581301 Oct 30 '17 at 23:36
  • 1
    This line looks suspect: Node root = *new Node(); // Why not just instantiate as: Node root; instead? – kvr Oct 30 '17 at 23:38
  • @kvr tried both ways, neither makes a difference. I had just forgotten to change it back. – Dez Oct 30 '17 at 23:41
  • Regarding `#include "Trie.cpp"`: it is recommended that you do not include cpp files. Include headers and compile source files. – user4581301 Oct 30 '17 at 23:41
  • Program crashes over invalid argument to `stoi` when I input "a b c d e". What is a good input set to trigger the error you are receiving? – user4581301 Oct 30 '17 at 23:46
  • @user4581301 input needs to start with a number so: '5 the dog sits' works or any other number followed by a sentence – Dez Oct 30 '17 at 23:49
  • Check and make sure the child node, or 'Node' is not null before attempting to use it to call getWord(). You are calling getWord using a nested call, so it won't be obvious if the node you are accessing is null. – kvr Oct 30 '17 at 23:49
  • Unrelated: The `add` routine may benefit from passing `phrase` by reference. H,mmm. Maybe not. You instantly copy it into sentence. since phrase is already a copy and you never use it again , you can do away with sentence. – user4581301 Oct 30 '17 at 23:53
  • @user4581301 I thought I would need to use sentence since every time i recursively call add I remove the first element. – Dez Oct 30 '17 at 23:58
  • Passing in `phrase` by value eliminates the need to do this. `Phrase` inside `add` is a copy of the variable it was called with, so you can modify it until the cows come home and not affect the source variable. – user4581301 Oct 31 '17 at 00:00
  • @user4581301 that makes sense. Any idea what is causing the bad access error? – Dez Oct 31 '17 at 00:03

1 Answers1

0

Do you have some experience with Java, by chance? In any case, there are a couple of important things to note about C++: a simple assignment or initialization does not link a variable as a reference to an existing object, and the keyword new is not needed to create new objects.

Node current = this->root;

This line, in both add and addPhrase, creates a Node object which is a copy of your root node (children and all). So anything you do to current won't affect root. And at the very end of main, the line

cout << myTrie.root.children[0].getWord() << endl;

is invalid because myTrie.root.children is still empty, possibly causing your crash (unless I missed an earlier problem).

The new keyword in C++ creates an object with dynamic storage duration instead of the usual automatic storage duration, which means that the object will not be destroyed for any reason unless you use the delete keyword on a pointer to that object. So any time you do something like

Trie myTrie = *new Trie();

the program creates a Trie object without a name because of the new, then creates the myTrie object by copying from that object, but the first object will exist for the rest of the program and is considered "leaked". Too many leaks, besides being bad form, will increase your program's usage of computer memory in a way that can't be reversed until the program stops. To default-construct a Trie object, it's enough to just write:

Trie myTrie;

In add and addPhrase, you want your variable current to relate to different existing Node objects, not to be an independent Node that lives for the duration of the function. This is actually a use case for a raw pointer:

void addPhrase(vector<string> phrase, int weight) {
    Node* current = &this->root;
    for (int i = 0; i < phrase.size(); i++) {
        Node temp(phrase[i]);
        current->children.push_back(temp);
        current = &current->children.back();
        if (i == phrase.size() - 1) {
            current->setEOS();
            current->setWeight(weight);
        }
    }
}

(Note current->children.back() is a shorter way of saying current->children[current->children.size()-1].)

aschepler
  • 70,891
  • 9
  • 107
  • 161