Lab 8
You cannot submit for this problem because the homework's deadline is due.
EECS 280 Lab 8: Deep Copies and The Big Three
Lab Due Friday, June 21, 2024, 11:59 pm
Unfortunately, it turns out our ArrayIntVector
from the previous lab still has a few issues that can lead to memory errors, since we only implemented a custom destructor and not all Big Three. In this lab, we will implement a copy constructor and assignment operator to fix up ArrayIntVector
.
Completion Criteria/Checklist:
To pass this lab, you must finish tasks 1, 2, and 3.
Lab Exercises
The Files
Here's a summary of this lab's files.
File | Description |
---|---|
ArrayIntVector.h |
Contains the interface of ArrayIntVector . |
ArrayIntVector.cpp |
Contains the implementation of ArrayIntVector . |
lab08.cpp |
Contains the main function that runs testing code. |
Testing Code
The main
function in lab08.cpp
contains testing code we've written for you, printing the correct results and those produced by your code for you to compare.
The starter code should compile successfully without any modifications, so make sure you are able to compile and run it with the following commands. The code may be missing some pieces, contain some bugs, or crash when you run it, but you'll fix each throughout the course of the lab.
$ g++ -Wall -Werror -g -pedantic --std=c++17 ArrayIntVector.cpp lab08.cpp -o lab08.exe
$ ./lab08.exe
Introduction
The Big Three
Copy Constructor
A copy constructor is used whenever we are creating a new copy of some object "from scratch".
ArrayIntVector v;
ArrayIntVector v_copy(v); // Copy Constructor
In this declaration, we're making a copy of v
called v_copy
. Because v_copy
didn't exist before this, the compiler will use the copy constructor. (Think: we're constructing a new vector based on v
.)
Warning! Some statements that look like assignments actually use the copy constructor.
ArrayIntVector v;
ArrayIntVector v_copy = v; // Copy Constructor
This is not actually an assignment expression - it's a variable definition, and it uses the copy constructor. Again, notice that v_copy
doesn't exist before this statement.
Assignment Operator
The assignment operator is used when we want to assign (copy) the value of one object to another. As opposed to the copy constructor, when we do assignment we don't actually create any new object - it already exists! For example:
ArrayIntVector v;
ArrayIntVector v_other;
v_other = v; // Assignment operator
In this example, v_other
is created on the second line (using the default constructor), so when we reach the assignment it just copies the value of v
into v_other
.
If you find cheesy analogies helpful, the copy constructor is instructions for cloning someone, whereas the assignment operator is more like identity theft.
Destructor
A destructor is called when a class-type object dies. Different objects die at different times - for example, statically allocated variables die when they go out of scope, whereas dynamically allocated variables live until they are explicitly killed with delete
.
Basically, a destructor is where C++ gives each object one last chance to put its affairs in order before the lights go out.
Implicit Big Three
Behind the scenes, C++ uses the Big Three for every class-type object, so if you don't provide your own custom ones the compiler will provide implicit versions for you. Basically, the implicit version of each just iterates through all the member variables and does the following according to its type.
Member | Copy Constructor | Assignment Operator | Destructor |
---|---|---|---|
Primitive | Basic copy | Basic copy | Nothing |
Pointer | Basic copy | Basic copy | Nothing |
Class-type object | Copy using object's copy constructor | Copy using object's assignment operator | Nothing |
Member variables that are arrays are handled element-by-element in the same fashion as shown in the table, depending on the type of the elements in the array (Only for actual array members, not for pointers to dynamic arrays). As with an explicit destructor, the implictly defined destructor automatically destroys all members and invokes the base-class destructor (if the type has a base class) after the destructor's body runs.
Custom Big Three
In some cases, the implicit Big Three aren't good enough to properly manage an object's resources. For example, if your constructor allocates dynamic memory, then you'll need a custom destructor that cleans up that memory. You will also need a custom copy constructor and assignment operator to properly perform deep copies of the object.
Rule of the Big Three
If you need to implement a custom version of one of the Big Three to properly manage an object's memory, you almost certainly need to implement a custom version of the others as well.
Task 1 - Copy Code from Previous Lab
In the starter files you'll find a new version of the ArrayIntVector
class we've been working on in the last few labs. However, the copy constructor and assignment operator are not fully implemented yet.
Start by copying your solutions from the previous lab for the destructor, push_back
, and grow
before proceeding to the next task.
Task 2 - Copy Constructor
We need the copy constructor for ArrayIntVector
to perform a deep copy. We need to copy the (dynamically allocated) array storing the data rather than just the pointer to that array.
Implement the custom copy constructor so that it fully implements these steps:
Copy over the
capacity
andnum_elts
members - you should do these in a member initializer list.Copy the
data
array:Create a new dynamically allocated array in a member initializer list.
Copy over each element in the constructor body.
Note: Make sure to use the member initializer list to initialize the members or delegate to another constructor. If you do not do so, the members will be default initialized, and it will be extra work to assign them later in the body of the constructor.
Task 3 - Assignment Operator
Proceed to implement the assignment operator. Again, we need to make sure we do a deep copy. But there are a few other things we also need to worry about. Consider again the assignment example from above:
ArrayIntVector v;
ArrayIntVector v_other;
v_other = v; // Assignment operator
Because v_other
already exists, it's going to have its own dynamically allocated data array that we need to get rid of when we copy over the value from v
. We also need to check for self-assignment (i.e. v = v
), in which case we just do nothing.
Note: Remember that inside any member function we can refer to the current object using the this
pointer. The object on the right-hand side of the assignment operator, whose value we are copying from, is the parameter rhs
.
Implement the custom assignment operator so that it fully implements these steps:
Check to see if we're doing self-assignment, and if so, just
return *this
(which is the object being assigned to).Clean up the dynamic memory used for the old array using
delete[]
.Copy over the
capacity
andnum_elts
members.Copy the
data
array by creating a new dynamically allocated array and then copying over each element.Return
*this
.
Now, your ArrayIntVector
should finally manage its memory correctly!
Submit
Submit all the files to the autograder.