CSE 428S: Studio 10

Dynamic Memory Management


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.


Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.
  2. 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

    1. A static member variable of type size_t that is initialized to 0 and will be used to track how many objects of that class have been constructed
    2. A non-static member variable of type size_t that (along with its memory address that is stored in its this pointer) identifies the object
    3. A default constructor that initializes the non-static member variable with the value of the static member variable, increments the static member variable, and then prints to the standard output stream a message indicating that the default constructor was called and what the non-static member variable's value and the object's address are
    4. A copy constructor that initializes the non-static member variable with the value of the non-static member variable of the object from which it is being constructed, increments the static member variable, and then prints to the standard output stream a message indicating that the copy constructor was called and what the non-static member variable's value and the object's address are
    5. A destructor that prints to the standard output stream a message indicating that the destructor 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)

    In 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

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

    Optional Enrichment Exercises

  8. 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.

  9. 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.

Things to Turn In:

For this studio, please turn in the following:


Page posted Wednesday September 28, 2022, by Chris Gill.