Classes come with a destructor (just calls destructors for all fields that are objects)
What happens when an object is destroyed?
destructor body runs
fields' destructors are invoked in reverse declaration order (for fields that are objects)
space deallocated
When do we need to write a destructor?
Node *np=new Node {1, new Node {2, new Node {3, nullptr}}};
If np goes out of scope
pointer np is reclaimed (stack-allocated)
the list is leaked
If we say delete np;:
Write a destructor to ensure the whole list is freed:
struct Node {
...
~Node() {
delete next;
}
}
Now - delete np; frees the whole list
invokes *next's destructors using recursion
therefore, the whole list is deallocated
Copy Assignment Operator
Student billy {60,70,80};
Student jane=billy; // copy constructor
Student joey; // default constructor
joey = billy; // copy, but not a construction, it is the copy assignment operator -uses compiler-supplied default
You may need to write your own copy assignment operator:
// WRONG AND DANGEROUS WAY:
struct Node {
...
// so that cascading works (eg. a=b=c=d;)
Node &operator = (const Node &other) {
data = other.data;
next=other.next?new Node{*other.next*}:nullptr;
return *this;
}
}
Why is this way dangerous?
Node n {1, new Node {2, new Node {3, nullptr}}};
// deletes n and then tries to copy n to n
// undefined behaviour
n=n;
When writing operator=, always watch out for self-assignment:
struct Node {
...
Node &operator=(const Node &other) {
if (this == &other) return *this;
data = other.data;
delete next;
// if "new" fails, this method will abort
// next has been deleted, but not reassigned, therefore we have pointers at dead memory which means we have a corrupted list
next = other.next? new Node {*other, next}: nullptr;
return *this;
}
}
Better Way:
Node &Node::operator=(const Node &other) {
if (this == &other) return *this;
Node *tmp = next;
next = other.next? new Node {*other.next}:nullptr;
data = other.data
delete tmp;
return *this; // if new fails, node will still be in its original state (not corrupted)
}
Note:
*other.next // equivalent to *(other.next)
other->next // equivalent to (*other).next