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.

  • Copy your solution for the destructor, push_back, and grow from the previous lab.

  • Implement the copy constructor.

  • Implement the assignment operator.

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 and num_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 and num_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.

Lab 8

Not Claimed
Status
Finished
Problems
1
Open Since
2024-06-19 00:00
DDL
2024-06-21 23:59
Extension
72.0 hour(s)