-3

I'm a complete beginner in c++ and everything has been going on well until now. I'm new to the idea of pointers (I'm from python), and I have this weird error.

So basically, I created this "SearchNode" class, and found below is one of it's methods "getChildren" which should return a vector of other SearchNode instances, representing the possible cells to which a Knight (chessboard) could travel from it's current state. (BFS)

That said, when I finish pushing into my vector, all the elements suddenly point to 1st element only. Could someone help me out here?

PS: it's a similar problem to c++ push_back doesn't work as it is supposed ... but unlike Angela (who's was writing her own compiler), I'm a total beginner in c++. Your help with be greatly appreciated.

UPDATE

I got rid of the int*, and used array for my state. I could now successfully search the graph (therefore the states are ok) and find the shortest path, but I couldn't seem to reconstruct the path.

To test, I started at {0,0} and could find {4,4}, but the path, according to the getPath method was {4,4}, {3,6}, {3,6}, {3,6} ... (infinite loop of {3,6}). Is there something wrong with my parent pointers, or my getPath function? Thanks for your support in advance.

//Search class
class SearchNode
{
public:
//Variables
SearchNode *m_parent;
array<int,2> m_state; //I don't understand typedef's yet, will use them when I'm clearer with them :)

//Normal Constructor
SearchNode(array<int,2>& state_, SearchNode *parent_=nullptr) :
m_state(state_),
m_parent(parent_)
{}


//Method to get Next reachable states. Returns instances of SearchNode.
vector<SearchNode> getChildren()
{
    int legalMoves[8][2] = {{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};

    vector<SearchNode> children;
    children.reserve(8);
    for(int i=0; i<8; i++)
    {
        int x = (m_state[0] + legalMoves[i][0]);
        int y = (m_state[1] + legalMoves[i][1]);
        if( (x>-1) and (x<9) and (y<9) and (y>-1)) // Within the bounds of the board
        {
            array<int,2> childState = {x,y};
            SearchNode childNode = SearchNode(childState,this);
            children.push_back(childNode);
        }
    }
    return children;
}

void getPath()
{
    cout<<"\nPath: ";
    cout<<  this->print();
    SearchNode current = *this;
    unsigned int counter = 1;
    while((current.m_parent!=nullptr) and counter< 10)
    {
        counter++;
        cout<< (current.m_parent)->print();
        current = *(current.m_parent);
    }
    cout << (current.m_parent)->print();
}

string print()
{
    stringstream out;
    out << "{" << this->m_state[0] << "," << this->m_state[1] << "} ";
    return out.str();
}
};
Community
  • 1
  • 1
John
  • 7
  • 4
  • 4
    Does `SearchNode` have a reasonable copy constructor? – πάντα ῥεῖ Jun 01 '13 at 16:54
  • 3
    Are you taking care of [this](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three)? – Hans Passant Jun 01 '13 at 17:02
  • Have you watched the for loop in a debugger? – ChiefTwoPencils Jun 01 '13 at 17:05
  • @g-makulik I am really a beginner, I don't know what you mean. So, I guess no. :D – John Jun 01 '13 at 17:31
  • @John Just post the code for your `SearchNode` constructor(s). – ApproachingDarknessFish Jun 01 '13 at 17:32
  • @HansPassant What do you mean by taking care of this :) ? The big idea (as I was thinking) was to have a pointer to the parent, so I could reconstruct the path using a predecessor relationship :) . In python, I use `self`, in c++ I thought of `this` as the pointer to the instance... should I know something else? – John Jun 01 '13 at 17:34
  • @ValekHalfHeart done. Please don't laugh at me... I'm still a beginner. :D – John Jun 01 '13 at 17:38
  • @John: HansPassant posted a link where it says "this", he didn't mean "this" as in the invoking-object pointer. – Sion Sheevok Jun 01 '13 at 17:39
  • 3
    Raw pointers, e.g. `int*` don't own what they're pointing to. When you write `SearchNode(childState, this)`, `childState` is not copied, but `childNode.state` points to `childState[0]`. The latter is destroyed when `childState` goes out of scope, then the pointer is a *dangling pointer*. As you have not specified a copy-ctor for `SearchNode`, the object inserted into `children` has the same problem. – dyp Jun 01 '13 at 17:43
  • Ah, so blue means a link? :D I'm totally new to this :D Thanks. I'm reading that link right now. – John Jun 01 '13 at 17:44
  • @John No worries, we all were once :) – ApproachingDarknessFish Jun 01 '13 at 17:47
  • "push_back doesn't work as it is supposed"? Tell that to the gazillion applications that use STL. – Daniel Kamil Kozar Jun 01 '13 at 18:11
  • @Dyp I read about copy constructors, and I think I understand what you are saying. Nonetheless I do not know how to fix it. Could you please give something to work on? Like a copy constructor that will get rid of this issue (safely). Thanks again. – John Jun 01 '13 at 19:38
  • You have problems applying the "Managing resources" section of the [first answer](http://stackoverflow.com/a/4172724/420683) of Hans Passant's link to your problem? – dyp Jun 01 '13 at 21:57
  • I added a copy constructor. Is what I am doing right/the right way? I am so bad at this that I have another error message "segmentation fault" (I'm working on it...) :D – John Jun 02 '13 at 02:36
  • In the copy constructor `SearchNode *parent = other.parent;` creates a temporary variable called parent, assigns a value to it, discards it and leaves the member "parent" unassigned. – kfsone Jun 02 '13 at 04:38

1 Answers1

6

Lots of mistakes and errors, I strongly suggest you turn up the warning level in your compiler so you can get more information. With GCC/G++/Clang, try "-Wall" or "-Wextra", as moshbear points out.

Your nodes never get assigned the "parent" value, you're creating a "shadow" local variable called "parent" and assigning that. To avoid common errors like this, use a prefix or postfix for member variable names to separate them from local names, e.g. "m_parent" or "_parent".

You don't assign default values in your constructor, you explicitly leave the values uninitialized.

SearchNode()
{
    //do nothing
}

and then you introduce this garbage data in your pointer-based constructor, what you probably want is

SearchNode() : parent(NULL), state(NULL) {}

Your copy constructor is a disaster. You need to read up on and understand pointers and local variables.

//Start Node constructor. Still looking for an equivalent for null.
SearchNode(int *state)
{
    int genericStartState[2] = {-1,-1};
    SearchNode blankParent = SearchNode();
    SearchNode genericStart = SearchNode(genericStartState,&blankParent);
    this->parent = &genericStart;
    this->state=state;
}

Firstly, "blankParent" here is a local variable containing random data because of your current copy constructor. Secondly, you're taking the address of it - of a private, local variable, which is about to stop existing when you hit the "}" at the end of the routine.

"genericStartState" is also about to go out of scope.

And aside from that, I don't think you want or need this particular constructor.

But fundamentally, the bug in your subject, is because you do the same thing in your assignment loop -- you use a temporary, local array to store the new values, and then pass a pointer to that to your constructor. Since you are taking the address, it will be the same every loop.

    int childState[2] = { x, y };
    SearchNode childNode = SearchNode(childState,this);

This is why all of your nodes have the same state - because they all point to the same memory location (edit: as pointed out by DyP, that side-effect isn't something you can count on, just an artefact of ordering in this case).

It might be easier for you to use simple array of ints rather than a pointer in your node structure.

Here's how the constructor side of things might look, if your compiler is VisualStudio 2012 or G++ 4.8 or Clang 4.2.

class SearchNode
{
public:
    typedef std::array<int, 2> State;

private:
    // I use the 'm_' convention for members, 'g_' for globals, 's_' for statics.
    SearchNode* m_parent;
    State       m_state;

public:
    //////////
    // Default ctor.
    SearchNode()
        : m_parent(nullptr) // C++11 constant meaning pointer with value 0
        , m_state({-1, -1}) // preferred but requires recent C++11 features
    {
        //m_state[0] = m_state[1] = -1; // had to do this instead for gcc 4.7.3
    }

    //////////
    // Normal ctor
    // I use the '_'-postfix convention for parameter names.
    SearchNode(SearchNode* parent_, const State& state_)
        : m_parent(parent_)
        , m_state(state_)
    {
    }

    //////////
    // Copy constructor.
    // We could do this, but it's the default behavior anyway.
    /*
    SearchNode(const SearchNode& rhs)
        : m_parent(rhs.m_parent)
        , m_state(rhs.m_state)
    {
    }
    */

    // Current C++11 compilers let us be explicit and do this:
    //SearchNode(const SearchNode& rhs) = default;

    // But it's the default behavior so we don't have to do this one at all.
};

The latest C++11 language changes (MSVC > 2012, GCC >= 4.8, Clang >= 4.1) would allow you to replace the first two constructors with

// Kill two birds with one stone and have default parameters on our normal ctor,
// replacing both the default and normal ctor with one function.
SearchNode(SearchNode* parent_ = nullptr, const State& state_ = { -1, -1 }))
    : m_parent(parent_)
    , m_state(state_)
{       
}

If you had a fully C++1y compatible compiler, you could boil all that down to:

class SearchNode
{
public:
    typedef std::array<int, 2> State;

private:
    // I use the 'm_' convention for members, 'g_' for globals, 's_' for statics.
    SearchNode* m_parent = nullptr; // c++1y keyword to replace 'NULL'
    State       m_state = { -1, -1 };

public:
    SearchNode() = default;
            SearchNode(const State& rhs_) = default; // not strictly required.
    SearchNode(SearchNode* parent_, const State& state_)
        : m_parent(parent_), m_state(state_)
        {}
};
kfsone
  • 23,617
  • 2
  • 42
  • 74
  • For GCC, -Wextra is also a wise idea when cranking up the warning level. I haven't tested the quality of the pedantry in clang when -Wextra is used, though, but for GCC, it's helped me with quite a few brain-fart bugs. – moshbear Jun 02 '13 at 05:43
  • @moshbear added that to the text – kfsone Jun 02 '13 at 05:48
  • @kfsone Thanks so much for the time an effort you placed in pointing out my errors. To be totally honest, you almost discouraged me, but I looked at it from a different perspective, and now I can't wait to reach your level of experience in this language. :D I have no one to teach me C++, but I think with replies like yours, I don't really need a teacher :D Thanks again. – John Jun 02 '13 at 13:52
  • 2
    "Since you are taking the address, it will be the same every loop." It can happen, but it doesn't *have to* happen. I.e. there's no guarantee from C++ that it'll be the same address for every loop iteration. – dyp Jun 02 '13 at 13:55
  • I changed to array . That said, I'm having trouble with this line he suggested: SearchNode() : parent(NULL), state(NULL) {}. Is that NULL supposed to be something a compilers knows to ignore? Because I'm getting "No matching function for call to std::array::array(NULL) " Error. – John Jun 02 '13 at 15:06
  • @John NULL is join the value 0, but given a pretty name so humans can tell you mean ["a pointer to nothing"](http://c-faq.com/null/null1.html). The error is because `std::array` doesn't have a constructor that takes a pointer, or a numeric value, as it's arguments. If your compiler has all the latest C++11 and C++1y changes, you can do `m_state({-1, -1})` but otherwise you'll have to do it by assignment in the constructor's body. – kfsone Jun 02 '13 at 16:41
  • @John See the extended examples I added to the answer :) – kfsone Jun 02 '13 at 17:20
  • @kfsone Thanks SOOOOO much! Ok. I'm updating my code with what I have now. I'm still to understand what `typedef` is and why this one works (almost). If I may ask, please why do you have "const" in const State& ... in the constructor parameters? – John Jun 03 '13 at 15:15
  • I tend to think of it as, informally, "alias". – kfsone Jun 03 '13 at 20:03
  • Ok. I give up. So why can't I get path? Please even if you have to down vote, atleast tell me what I'm doing wrong. :( – John Jun 05 '13 at 22:54
  • Hadn't seen the edits you made. The path bug is not any kind of magical language feature or anything, it's actually just a data problem. Grab a pen and paper and visualize for yourself what getChildren and getPath do, the relationships between the data and how you are trying to traverse them. It's not an uncommon pattern so solving it this time is going to be a huge boon to you down the line. – kfsone Jun 06 '13 at 05:03