Node *np = new Node{1, new Node{2, new Node{3, nullptr}}};
delete np; // calls destructor on Node (1); Node (2) & (3) leak
- every class comes with a destructor
- destructor calls destructors on fields that are objects
Node::~Node() {
delete next; // deleting a nullptr is safe
}
Student billy{80,50,70};
Student bobby{billy}; // copy constructor
Student jane{...}
jane = billy; // copy assignment operator
jane.operator=(billy); // operator= is a method
Sometimes the one we get for free does not do the "correct" thing.
Node n1{...}, n2{...}, n3{...};
n2 = n1; // n2.operator=(n1);
n3 = n2 = n1; // n3.operator=(n2.operator=(n1))
Node &Node::operator=(const Node &other) {
if (this == &other) return *this; // (2)
data = other.data;
delete next; // (1)
next = other.next ? new Node{*other.next} : nullptr;
return *this;
}
(1) next
might already be pointing to heap memroy
n1 = n1; // n1.operator = (n1);
next
andother.next
are the same- accessing a dangling pointer
(2) Self-Assignment Check
- If
new
fails to allocate memory,next
is not assigned. - Makes
next
a dangling pointer - Better to delay deleting next until we know that new will not fail
Node &Node::operator=(const Node &other) {
if (this == &other) return *this;
Node *temp = next;
// If this line fails, no lines after this will be run
next = other.next ? new Node{*other.next} : nullptr;
delete temp; // delaying the deleting to see if the above line fails
data = other.data;
return *this
}
// node.h
struct Node {
void swap(Node &other);
Node &operator=(const Node &);
};
// node.cc
#include <utility>
void Node::swap(Node &other) {
std::swap(data, other.data);
std::swap(next, other.next);
}
Node &Node::operator=(const Node &other) {
Node tmp{other}; // deep copy. Memory allocated on the stack
swap(tmp);
return *this;
}
// pass-by-value
Node plusOne(Node n) {
for (Node *p {&n}; p ; p = p->next) {
++p->data;
}
// return by value
return n;
}
int main() {
Node n {1, new Node{2, nullptr}};
Node n2 {plusOne(n)}; // 6 calls to copy constructor
cout << n << endl << n2 << endl;
}
- 2 calls to basic constructor
- 2 calls to copy constructor (to copy arguments)
- 2 calls to copy constructor (to return)
- Node n2{returned value}
- 2 calls to copy constructor (construct n2 as copy of returned value)