views:

141

answers:

5

This is supposed to traverse a BST and delete every node, including the root node. However, at the end, I get the message "root still has a left node." Why aren't all nodes deleted?

void deleteTree()
{   
    deleteNode(root);
    if(root->right)
        cout << "root still has a right node" << endl;
    if(root->left)
        cout << "root still has a left node" << endl;
    root = 0;

}   

void deleteNode(node *p) 
{   
    if(p->left) 
    {   
        deleteNode(p->left);
        p->left = 0;
    }   
    if(p->right) 
    {   
        deleteNode(p->right);
        p->right = 0;
    }   

    cout << "Deleting node containing " << p->data << endl;
    delete p;
}   
+6  A: 

Your are deleting p at the end (root) and then trying to access its contents in deleteTree(), where root no longer points to allocated memory. The result is going to be undefined.

Peter Alexander
Why is it that when it checks the right node, it doesn't find anything, but when it checks the left node it finds something?
Phenom
Because `root` is just pointing to garbage memory. Other results could include left only, both, neither, or a crash.
Potatoswatter
+3  A: 

You're deleting root. And then your code is trying to access memory where it was.

You're well into undefined-behaviour land there.

Anon.
+2  A: 

You should not dereference root after you delete it in deleteNode. Use a debugger to inspect why root->left is non-null.

Dominic Cooney
+1 for debugger suggestion, though I think he'll find that it is, in fact, null when the function returns.
Anon.
‘root‘ is certainly deleted, but there must be something else at ‘root->left‘; maybe sniffing around that address with the debugger will reveal something...
Dominic Cooney
+2  A: 

You are looking at root->left after you've already deleted root, making it avalable for use in a new allocated block.

James Curran
A: 

I would simply change the tree itself, it would be easier to deal with it then:

struct Node
{
  Node(data_type data): mLeft(), mRight(), mData(data) {}
  Node(const Node& rhs): mLeft(), mRight(), mData(rhs.mData)
  {
    if (rhs.mLeft.get()) mLeft.reset(new Node(*rhs.mLeft));
    if (rhs.right.get()) mRight.reset(new Node(*rhs.mRight));
  }
  Node& operator=(Node rhs)
  {
    this->swap(rhs);
    return *this;
  }
  ~Node() { }

  void swap(Node& rhs)
  {
    using std::swap;
    swap(mLeft, rhs.mLeft);
    swap(mRight, rhs.mRight);
    swap(mData, rhs.mData);
  }

  Node* left() const { return mLeft.get(); }
  void left(std::auto_ptr<Node> node) { mLeft= node; }

  Node* right() const { return mRight.get(); }
  void right(std::auto_ptr<Node> node) { mRight = node; }

  data_type& data() { return mData; }
  const data_type& data() const { return mData; }

private:
  std::auto_ptr<Node> mLeft;
  std::auto_ptr<Node> mRight;
  data_type mData;
};

By being Object-Oriented, each Node is now responsible for the memory it handles. Also, using std::auto_ptr in the interface makes it clear that it takes ownership.

Note that it's been tailored for deep-copying, any other approach requiring boost::shared_ptr or equivalent. And yes std::auto_ptr leaves you dealing with copying by yourself, no magic there.

This design is much cleaner than using a plain C-struct with everyone being able to manipulate the resources. You still have full access to the underlying data via the accessor... but THEY take care not to invoke undefined behavior...

Of course, you can still crash it down:

Node& node = ...
delete node.left(); // haha

But if C++ may protect against unintended issues, it leaves the door open to evil code.

Matthieu M.