lab

EECS 280 Lab 8: Deep Copies and The Big Three

Lab Due Sunday, November 8, 2020, 8:00 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. Finally, we’ve prepared an Exam Prep that reviews similar topics.

You may work alone or with a partner. Please see the syllabus for partnership rules.

Submit the code files below on the autograder. We encourage you to complete the lab Exam Prep, but it is not turned in for credit.

Files to submit

Completion Criteria/Checklist:

To pass this lab, you must finish tasks 1, 2, and 3.

Lab Exercises

The Files

We have provided starter files for this lab. Use the following commands in a terminal at your working directory to download the files.

$ wget eecs280staff.github.io/lab/lab08/starter-files.tar.gz
$ tar -xvzf starter-files.tar.gz

Here’s a summary of this lab’s files. You will turn in the bolded ones.

File Description
BigThree.cpp Contains example code with the Big Three.
ArrayIntVector.h Contains the interface of ArrayIntVector.
ArrayIntVector.cpp Contains the implementation of ArrayIntVector.
lab08.cpp Contains the main function that runs testing code.

Note: BigThree.cpp is not included in the starter files. Instead, it is available in Lobster (more info below).

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++11 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 0 - Lobster Group Exercise

You will work on this task using Lobster (https://lobster.eecs.umich.edu/).

To get started, sign in using your university account and select the lab08_BigThree.cpp file from the EECS280 Code section on the left-hand side. Click the Save button at the top of the editor to create a personal copy. Now you can click on Simulate to step through the code.

It takes some practice before the Big Three feel natural. Let’s take a look at the situations in which each of them are used. We’ve created a class called BigThree whose implementations of the Big Three functions print a message when they run. Try to predict what the code will output without actually running anything. Afterward, step through the simulation to check your work.

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:

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:

Now, your ArrayIntVector should finally manage its memory correctly!

Submit

Submit the required files to the autograder.