Object does not point to NULL after deleting

180 Views Asked by At

I have a Node class. The members are an int id, and a pointer to the next node: Node* next. I implemented the constructor and destructor as follows:

#include <iostream>

class Node
{
public:
  int id;
  Node *next;
  Node() : id(0), next(nullptr) {}
  explicit Node(int id) : id(id), next(nullptr) {}

  ~Node() {
    std::cout << (*this) << " destructed \n";
    delete next;
    next = nullptr;
  }

  friend std::ostream &operator<<(std::ostream &os, const Node &node) {
    os << "Node(" << node.id << ")";
    return os;
  }
};

int main()
{
  Node *node0;
  Node *node1;
  node0 = new Node(0);
  node1 = new Node(1);
  node0->next = node1;

  delete node0; // outputs Node(0) destructed\nNode(1) destructed
  node0 = nullptr;
  std::cout << (nullptr == node0) << '\n'; // outputs 1
  std::cout << (nullptr == node1) << '\n'; // outputs 0
  std::cout << *node1 << " \n"; // outputs Node(8430)
  return 0;
}

I thought node1 should already point to nullptr since when calling the destructor for node0, its next is deleted and point to nullptr, but the output suggests that node1 is apparently Node(8430) and is not a nullptr.

I know that when dereferencing a nullptr it could result in undefined behavior. How can I modify this program so that after calling delete node0, node1 can also become nullptr?

2

There are 2 best solutions below

10
463035818_is_not_an_ai On BEST ANSWER

What you expect is similar to this:

void foo(int* ptr) {
    delete ptr;
    ptr = nullptr;
}

int main() {
   int * x = new int;
   foo(x);
   assert( x == nullptr);   // NOPE !!
}

ptr is a copy of x. delete ptr deletes the dynamically allocated int. That does not affect the value of x in any way. x still has the same value after calling foo. It just is not a valid pointer anymore. The address where it points holds no int anymore.

Or even simpler:

int* a = new int;
int* b = a;
delete a;
a = nullptr;
assert( b == nullptr ); // NOPE !!!

Actually your misunderstanding can be traced back to an even simpler example:

int a = 42;
int b = a;
a = 0;
assert( b == 0); // NO !!

With pointers it is no different.

In your code node0->next = node1; a copy of the pointer is made. Then in the destructor of node0 you delete the node pointed to by node0->next but that has no effect on the pointer in main.

TL;DR: Do not confuse the pointer and the pointee. delete x deletes the object pointed to by x. If you copy a pointer you have two totally independent pointers that point to the same object, assigning nullptr to one of them has no effect on the other.

0
ABaumstumpf On

There already is the answer by 463035818_is_not_an_ai which explains the misunderstanding - but there is the second part to this: It can be done - kinda.

#include <iostream>

class Node {
public:
  int id;
  Node *next;
  Node **holder{ nullptr};
  explicit Node(int id ) : id(id), next(nullptr) {}
  Node(int id, Node** ptr) : id(id), next(nullptr), holder(ptr) {}

  ~Node() {
    std::cout << "Node with id(" << id << ") destructed \n";
    delete next;
    if( holder ) *holder = nullptr; // make sure not to write to nullptr
  }

  friend std::ostream &operator<<(std::ostream &os, const Node &node) {
    os << "Node(" << node.id << ")";
    return os;
  }
};

Capture the address of the pointer that holds the node.

int main() {
  Node *node0;
  Node *node1;
  node0 = new Node(0, nullptr);
  node1 = new Node(1, &node1);
  node0->next = node1;

  // delete node1; // causes double-free
  delete node0; // outputs Node(0) destructed\nNode(1) destructed
  node0 = nullptr;
  std::cout << (nullptr == node0) << '\n'; // outputs 1
  std::cout << (nullptr == node1) << '\n'; // outputs 0
  //std::cout << *node1 << " \n"; // will crash
  return 0;
}

This would be a "simple" way of achieving the outcome you expected but it would be exceedingly bad style and error-prone. In general you do not want a dynamically allocated object to know who/where it is held from and this simple example would just work with a single point - nothing prevents you from creating another copy of the pointer before that.

Having a list where a parent-node deletes the child-node also can lead to nasty errors as shown by the line delete node1; - when deleting node0 it will still try to delete node1 yet again causing a double-free.