Today's lecture and studio focus on memory management, both when using smart pointers and other templates for safe memory allocation and deallocation as is recommended, and also when using lower-level operators and templates (especially to give a sense of the potential pitfalls that may arise when doing that). The abstractions and techniques covered in today's lecture and studio are fundamental to modern C++ programming, and are used throughout the standard libraries that you have used in the studio and lab assignments so far.
Please complete the following required exercises, and any of the optional enrichment exercises that interest you. I encourage you to please work in groups of 2 or 3 people on each studio (and the groups are allowed to change from studio to studio) though if you would prefer to complete any studio by yourself that is also allowed.
As you work through these exercises, please record your answers, and when
you finish them please log into Canvas, select this course in this semester, and
then upload a .txt
file containing your answers on the Canvas page
for this studio assignment. Only one submission per team, please, and if
you need to re-submit it the person who originally submitted the studio should
please be the one to do that.
Make sure that the name of each person who worked on these exercises is listed in the first answer, and make sure you number each of your responses so it is easy to match your responses with each exercise.
First, ssh into shell.cec.wustl.edu
using your WUSTL
Key id and password and then use qlogin
to log into one
of the Linux Lab machines and then confirm that the version of g++
there is correct (as you did in Studio 0). Then
cd
into your directory for this course this semester, create a new
subdirectory for this studio, and cd
into it.
Copy the Makefile
from one of your previous studios into that
directory, and add a source file for your program's main function. As you
work on this studio, please update the Makefile as needed so that it will build an
executable program called studio10
from the files in that directory.
Add a header and source file for a class that has
size_t
that is initialized to 0 and will be used to track
how many objects of that class have been constructedsize_t
that (along with its memory address that is
stored in its this
pointer) identifies the objectIn your program's main function, declare an object of that class type on the stack, copy construct another object of that type from it, and then return a descriptively named symbol whose value is 0 to indicate success.
Build and run your program, and as the answer to this exercise please show (1) the output your program produced, (2) the declarations and definitions for the class you wrote, and (3) the code for your program's main function.
Remove the contents of your main function, except for the statement at the end that returns a value indicating success.
In your main function use the array ([]
) syntax for the
new
operator to allocate a dynamic array of objects of your
class type, store the address that was returned in a plain-old-C++ pointer,
and then use the array ([]
) syntax for the delete
operator on that pointer to destroy that array of objects. Build and run
your program, and observe the output that it produces.
In your main function, replace the array version of delete
with
plain old delete
(without []
) on the pointer. Build
and run your program, and as the answer to this exercise please explain briefly
what happens, and what problem that reveals may occur if the non-array version
of delete
is used to free memory that was dynamically allocated
using the array version of new
.
Remove the contents of your main function, except for the statement at the end that returns a value indicating success.
Declare a shared_ptr
that is parameterized with your class type,
and initialize it with a call to make_shared
that takes no arguments.
Declare another shared_ptr
that is parameterized with your class type,
and initialize it with a call to make_shared
that takes a dereference
of the first shared_ptr
as its only argument.
Build and run your program, and confirm that it produced the same output as
the previous exercise. As the answer to this exercise, please show the
statements that declare and initialize the shared_ptr
variables
in your program's main function.
In the header and source files for your class, declare and define a public member function that prints to the standard output stream a message indicating that it was called and what the non-static member variable's value and the object's address are (but does not modify the static member variable).
Just below the shared_ptr
declarations in your program's main
function, declare a weak_ptr
and initialize it with the
first shared_ptr
that you declared. Then, declare
another shared_ptr
and initialize it with a call to the
lock
member function of the weak_ptr
.
If that shared_ptr
is equal to nullptr
print out a
message to the standard output stream saying that the weak_ptr
no longer points to a valid object; otherwise (1) use that shared_ptr
to call the public member function (that you just added to your class) on the
object to which it points and then (2) assign nullptr
to that
shared_ptr
to break its association with that object (that
last step is essential for the next exercise to work, since otherwise the
shared_ptr
would keep the object's reference count higher).
Build and run your program, and as the answer to this exercise, please show the output that your program produced.
Just below where you declared and used the weak_ptr
, add a statement
that assigns the second shared_ptr
you had declared
at the top of your main function, to the first
shared_ptr
you had declared at the top of your main function.
Before and after that statement, print messages to the standard output stream
indicating that your code is about to do that assignment, and then that it has
done that assignment, respectively (so you can see what happens and when, as a
result of that assignment).
Just below that, assign the third shared_ptr
you
had declared the result of a call to the lock
member function of
the weak_ptr
. If that shared_ptr
is equal to
nullptr
print out a message to the standard output stream saying
that the weak_ptr
no longer points to a valid object; otherwise
use that shared_ptr
to call the public member function (that you
added to your class in the previous exercise) on the object to which it points.
Build and run your program, and as the answer to this exercise, please (1) show
the output your program produced, and (2) explain briefly why you think that
the shared_ptr
reference counting semantics are working correctly,
based on what is shown by that output.
Remove the contents of your main function, except for the statement at the end that returns a value indicating success.
In the source file where you defined your program's main function, define a
function that takes no parameters, declares a unique_ptr
to your
class type, initializes the unique_ptr
with the address of an object
of your class type that is returned by a call to the new
operator,
and returns that unique_ptr
by value.
In that same source file, define another function that takes a reference to a
unique_ptr
to your class type, and uses it to invoke the public
member function of the object to which the unique_ptr
points.
In your program's main function, declare a unique_ptr
to your
class type and initialize it with a call to the first function you added to
that source file. Pass that unique_ptr
into a call to the second
function you added to that source file, and then use the unique_ptr
to invoke the public member function of the object to which it points.
Build and run your program, and as the answer to this exercise, please show the output that the program produced.
Remove the contents of your main function, except for the statement at the end that returns a value indicating success.
In the main function, declare a standard allocator
that is
parameterized with your class type, call its allocate
member function to allocate enough memory to hold a specific number of
objects of your class type, and store the address of that chunk of memory
in a pointer to an object of your class type.
Write a for loop that starts at the address stored in the pointer, and iterates
through the memory chunk using the placement form of new
to invoke
the default constructor at each object location within it. Then write a similar
for loop that iterates through the memory chunk and explicitly invokes the
destructor at each object location within it.
Compile and run your program and as the answer to this exercise please show the output that it produced.
In your main function, after the for loop that invokes the constructors on
the objects in the chunk of memory that the allocator had returned, call the
allocator's allocate
member function again to allocate enough
memory for that same number of
objects of your class type, and store the address of that chunk of memory
in another pointer to an object of your class type.
Pass pointers to the start and just past the end of the first chunk of memory
and to the start of the second chunk of memory into a call to the
uninitialized_copy
algorithm, to invoke copy constructors at
each object location within the second chunk of memory. Then write a for loop
that iterates through the second memory chunk and explicitly invokes the
destructor at each object location within it.
Compile and run your program, and as the answer to this exercise please show the output it produced.
For this studio, please turn in the following:
Page posted Wednesday September 28, 2022, by Chris Gill.