OOP Homework 3 --- Sample Solution Assignment 1 (20 Points): Consider the program hw3.1.cc, which uses the latest version of our smart pointer class. Provide the correctly ordered list of all method calls on objects of types Ptr and A that is observed when the program is executed. You must include calls to constructors, destructors, and overloaded operators in your list. However, you do not need to list the values of the parameters to these calls (if there are any). Explain each method call that you list (why does the call happen and how does it relate to the program's source code). Hint: use the TRACE macro to produce the list of all calls. Trace: 1: Ptr:66:0x1be3010 2: A:29:0x1be3050 3: Ptr:70:0x1be3010 4: Ptr:66:0x1be3050 5: ~Ptr:75:0x1be3010 6: Ptr:70:0x1be3050 7: operator->:97:0x1be3050 8: operator*:96:0x1be3010 9: ~Ptr:75:0x1be3050 10: ~Ptr:75:0x1be3050 12: ~A:31:0x1be3050 13: ~Ptr:75:0x1be3010 Explanations 1: line 37: constructor of temporary Ptr wrapping x in main before call to constructor of A 2: line 37: constructor of A called when new executing new 3: line 37/line 29: copy constructor of Ptr called when initializing A::x 4: line 37: constructor of Ptr called when initializing p in main 5: line 37: destructor of Ptr called when the Ptr created in "1:" is destroyed 6: line 39: copy constructor of Ptr called when initializing q in main 7: line 41: arrow operator of Ptr called on q 8: line 41: dereference operator of Ptr called on q->x 9: line 43: destructor of Ptr called when destroying q in main 10: line 43: destructor of Ptr called when destroying p in main 11: line 43: destructor of A called when deleting addr in ~Ptr of p 12: line 43: destructor of Ptr called when destroying A::x in ~A Assignment 2 (10 Points): Consider the programs hw3.2.cc and hw3.3.cc, which are variants of hw3.1.cc. Both of these programs can have undefined behavior when they are executed (e.g. crash). For each program, explain what is going wrong. hw3.2.cc: On line 40, we wrap a raw pointer to the _stack allocated_ variable x inside a smart pointer. This will cause problems because smart pointers should only be used to wrap raw pointers to heap objects. Stack allocated memory is automatically managed by code that is generated by the compiler. Indeed, at the return point of main the compiler automatically inserts a call to the destructor ~Ptr on "a", since "a" is stack allocated. The destructor in turn will call delete on the wrapped raw pointer, trying to free the stack allocated address of x, since it assumes that the address points to the heap. At this point the program will abort with an error or exhibit other undefined behavior. hw3.3.cc: On line 40 we wrap a raw pointer to a heap allocated A object in the smart pointer "a". The A object itself wraps a raw pointer to a heap allocated int value in the smart pointer A::x. So far so good. On line 41, we dereference the smart pointer "a", yielding a reference to the A object, and then take the address of that reference. That is, &*a yields the raw pointer stored in the private field addr of "a". Before the call to f on line 41 is executed, a temporary smart pointer is created to wrap the address &*a. At this point, we have two smart pointers pointing to the address &*a, the smart pointer "a" and the temporary smart pointer. These two smart pointers do not share a common counter. Instead, each has its own counter with value 1. We thus broke the contract of the smart pointer class, which requires that the counter value of a smart pointer always reflects the total number of smart pointers pointing to the same heap object. After the call to f on line 41 has terminated, the destructor of the temporary smart pointer is called. Since its counter is decremented to 0, the destructor ~Ptr in turn deletes the A object wrapped by the smart pointer, causing a call to its destructor ~A. In turn, the destructor ~A implicitly calls the destructor ~Ptr for its field A::x. After all destructors have returned, we are in a state where both x in main and a.addr are "dangling" pointers to invalid memory. Dereferencing any of these dangling pointers will have undefined behavior. In particular, the call to the arrow operator on "a" in line 42, will dereference a.addr. Similarly, when main returns on line 43, the destructor ~Ptr will be called on "a", which will again call delete on a.addr. We will thus call delete on an invalid address, which is an operation with undefined behavior.