How does Caroline’s initialization happen? It happens via the copy constructor which is invoked when one object is initialized with another of the same type.
Note: every class comes with (built-in):
-
a default constructor (just default constructs all fields which are objects)
-
a copy constructor (just copy initializes all fields)
-
a copy assignment constructor (just copies assigns all fields)
-
a destructor
-
a move constructor
-
a move assignment operator
To build your own copy constructor:
This copy constructor is identical to the behaviour of the compiler provided one. This then begs the question: When is the built-in copy constructor not sufficient?
Consider:
Above, n
, m
, p
are all on the stack, the data that n
, p
, and m.next
points at is on the heap. The built-in copy constructor just copy initializes all fields, each object has the same next point, so they all share tail (shallow copy)
If we want a deep copy, (so each list is a distinct copy of values, changing the data of the one tail does not affect the others) write your own copy constructor
Deep Copy:
The copy constructor is called when:
-
An object is initialized by another object of the same type
-
When an object is passed by value (need to copy into function’s stack frame)
-
When another object is returned by value, (must copy to the caller’s stack frame)
The statements are true for now, but we’ll see examples shortly
Note: be careful with constructors that take a single parameter
Now, we could accidentally pass the wrong argument to a function, (maybe we pass an int var when we meant to pass a node, would be nice to catch this error)
GOOD IDEA: disallow compiler from using this constructor for implicit conversions, by making it explicit
A ==copy constructor== is a member function that initializes an object using another object of the same class. The Copy constructor is called mainly when a new object is created from an existing object, as a copy of the existing object.
In C++, a Copy Constructor may be called for the following cases:
-
When an object of the class is returned by value.
-
When an object of the class is passed (to a function) by value as an argument.
-
When an object is constructed based on another object of the same class.
-
When the compiler generates a temporary object.
Here is an example of a linked-list deep copy
Destructors
When an object is destroyed (for stack-allocated object when they go out of scope, and for heap-allocated objects, when they are deleted), a method called the destructor runs, specifically the following sequence happens.
-
The destructor body runs
-
Destructors care involved for fields which are objects
-
Space is deallocated
- Classes come with a destructor (empty body) so does nothing (phase 2 of object destruction still runs)
- So, when do we need to write our own destructor?
This only deletes the node np
directly points at, not the rest in the list, we could iterate through the list freeing each node ourselves, but that is not out job, that Node owns its next node, it is responsible for cleaning it up (just like we own np
and are responsible for deleting it).
After deleting np, we have leaked the rest of the list. So, we write our own destructor:
Now, delete np;
frees the whole linked list, the node np
points at is destroyed, as such its destructor is ran, its destructor deletes its next
, that is a Node
object being destroyed, so …, etc. Until we reach delete next;
where next
is the nullptr
, delete nullptr
is a no-op (does nothing).