COMP 2012H Honors Object-Oriented Programming and Data Structures

Lab 7 - Generic Programming

Review


Generic Programming
Generic Programming (GP) means programming with types as parameters. C++ supports GP through the template mechanism.

  • Function templates allow you to create functions that work on different types of objects. For example, a max function template can be defined as follows:

    template T my_max(const T& a, const T& b) { return (a > b) ? a : b; }
  • Class templates allow you to create classes that support different types of data. For example, a general container class can be defined as follows:

    template <typename T>
    class List_Node
    {
      public:
        List_Node(const T& x) : data(x) { }
        T data;
    };
  • Operator overloading further allows the generic operator function syntax to work for objects of user-defined new types.

Introduction and Background

This lab is designed to give you some hands-on experience with simple Generic Programming using templates. This time, we will be implementing a Smart Pointer based on the STL.

There are several types of the Smart Pointers in the STL:

  • std::auto_ptr (has been removed in C++17)
  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr
In this lab, we will be implementing a simplified version of std::unique_ptr.


Background: Smart pointers

Smart pointers are essentially raw pointers with extra features. One of the most notable features is automatic memory management, which helps the user to avoid common dynamic memory issues such as dangling pointers and memory leaks. With Smart Pointers, we no longer need to worry about managing our calls to new and delete. Thus, using smart pointers are in a certain sense similar to the garbage collection functionality of Java.


std::unique_ptr

Unique pointers (std::unique_ptr) are essentially pointers with one extra assumption: there is only one pointer at a time pointing towards a particular object, hence "unique".

There are many cases where such an assumption would be useful. For example, suppose we have a raw pointer that is the only reference towards a dynamically allocated object. If we reassign this pointer to somewhere else, we would need to explicitly call delete on the original object to prevent memory leaks. This causes additionally trouble for the programmer. However, if we replace the raw pointer with std::unique_ptr, the original object will automatically be deleted during the assignment operation, as the object has lost its only pointer reference. Thus, we do not have to explicitly call delete anymore.

There are also cases where it is desirable to have multiple pointers pointing towards the same object. In this case, shared pointers (std::shared_ptr) should be used instead.


References

If you are interested in exploring more about smart pointers, take a look at the references below:

Tasks

Complete all the missing function definitions in my_unique_ptr.h.
Note: You are NOT allowed to define your own helper functions.

Overview

In this lab, we implement a minimal version of std::unique_ptr called my_unique_ptr. We use templating so that my_unique_ptr can store a raw pointer of any arbitrary type.

  • Each unique pointer of type T has a single data member T* p, the raw pointer held by the unique pointer. The object pointed by p is referred as the object owned by the unique pointer.
  • Unique pointers can be empty, i.e. null pointers that do not point to any particular object. Empty unique pointers have their p set to nullptr.
  • As explained previously, the implementation of unique pointers should maintain the assumption that there is only one unique pointer owning a particular object. For example, suppose we have unique pointers ptr1, ptr2 pointing towards the objects objA, objB respectively. If we want ptr1 to point to objB instead, we need to perform the following:
    • Destroy objA, since it has lost its only reference pointer.
    • Set ptr2 as empty, since it cannot own objB together with ptr1 at the same time.
  • In the next section, we will explain how to implement the different class members / operators, such that this unique pointer assumption is maintained.


Member Functions

Constructors
  • my_unique_ptr()
    • Default constructor. The unique pointer is initialized as empty.
    • p should be set to nullptr.
  • my_unique_ptr(T* p)
    • Parametrized constructor from an existing raw pointer p.
    • The unique pointer should point to p.
    • Note: For the purpose of this lab, you can assume that there exists no other unique pointers that point to the same p when this constructor is called.
  • my_unique_ptr(my_unique_ptr<T>& x)
    • Copy constructor from another unique pointer x. If x currently owns an object, the newly constructed unique pointer will take over ownership of that object.
    • If x is not empty, initialize the unique pointer such that it now points to the object owned by x. Then, set x as empty.
    • Otherwise, initialize the unique pointer as an empty pointer.

Destructor

  • ~my_unique_ptr()
    • If the pointer is non-empty, delete the owned object.
    • Otherwise, there is no need to deallocate anything.

Operators

  • my_unique_ptr& operator=(my_unique_ptr<T>& x)
    • Copy assignment operator.
    • The procedure is similar to the copy constructor; take over ownership of the object owned by x, and set x as empty.
    • Additionally, remember to destroy the previously owned object if the pointer is originally non-empty.
  • T& operator*() const
    • Returns the managed object (i.e. the result of dereferencing p).
    • You can assume that the unique pointer is not empty.
    • For any non-empty unique pointer mptr, *mptr should be equivalent to *p.
  • T* operator->() const
    • Returns the stored raw pointer p.
    • For any non-empty unique pointer mptr, mptr->foo() should be equivalent to p->foo().

Utility methods

  • T* get_pointer() const
    • Accessor for p.
    • This method is already implemented.
  • bool is_empty()
    • Returns whether the unique pointer is empty or not.
    • This method is already implemented.
  • T* release()
    • Releases ownership of the current owned object (if any), and returns the released pointer.
    • After calling this method, the unique pointer should be empty.
  • void reset()
    • Resets the unique pointer to an empty pointer.
    • If the unique pointer is previously non-empty, release and deallocate the previously owned object.
  • void reset(T* p)
    • Resets the unique pointer to a unique pointer owning p.
    • If the unique pointer is previously non-empty, release and deallocate the previously owned object.
  • void swap(my_unique_ptr<T>& x)
    • Swaps the unique pointer with x.
    • After swapping, the unique pointer should now own the object of x, and vice versa.

Resources

The output of the program is a series of test cases, that will test each member function, and print out "true" or "false" depending on your successful implementation. Your submission will get full mark as long as the following output is produced:

Constructor Tests:
Default Constructor... true
Parameterized Constructor... true
Copy Constructor and Sharing... true

Operator=() Tests:
Copy Assignment Operator... true

Reset Tests:
reset()... true
reset(T* p)... true

Swap Test:
swap()... true

Release Tests:
release()... true

Dereference and Pointer-to-Member Operator Tests:
operator*()... true
operator->()... true

Destructor Tests:
sw_temp should invoke destructors from sw_temp0 to sw_temp9.
sw_temp0 is destroyed.
sw_temp1 is destroyed.
sw_temp2 is destroyed.
sw_temp3 is destroyed.
sw_temp4 is destroyed.
sw_temp5 is destroyed.
sw_temp6 is destroyed.
sw_temp7 is destroyed.
sw_temp8 is destroyed.
sw_temp9 is destroyed.

sw1 and sw2 should invoke their destructors only once when main() exits.

sw2 is destroyed.
sw1 is destroyed.

Submission & Grading

Deadline: 14 November 2021 Sunday 23:59 HKT.
You may earn 1% course grade for this lab via Automated Grading on the ZINC Online Submission System.
Please compress and submit my_unique_ptr.h as lab7.zip to ZINC.

Page maintained by
Homepage