Object-Oriented Programming

CSCI-UA.0470-001

NYU, Undergraduate Division, Computer Science Course - Fall 2013

OOP Class Notes for 11/19/13

Smart Pointers

  • Smart Pointers
    • With smart pointers we can mostly do what the Java garbage collector does, reclaim unused objects automatically.
    • To do so, smart pointers need to keep track of the number of references.
    • As an added benefit, we can get rid of annoying upcasts in our traslator scheme.
  • Reference Count
    • We want to track of the number of references that point to objects. How to achieve this? Where to put the counter?
      • Inside the data layout. Don't implement this because code has to check for null values, as they have no data layout.
      • Inside our Ptr. Does not work. When two Ptrs point to the same object and one of them goes out of the scope, both counters in two Ptrs need to be decremented. But it is impossible to access the other Ptr.
      • In a seperate object and tag along with the actual object.
      • %
        • Update the counter when an object is declared, copied, destructed, and assigned. When the counter becomes zero, delete the object and its counter.
        • One disadvantage of reference counting: cannot locate circularly referenced objects that are not referenced from outside.
    • Implementation
      • Constructors
        /* size_t is a type used for storing unsigned integer independent 
           from architectures. size_t comes from the <cstring> library. 
           The default addr = 0 is for initializing array members. */
        
        Ptr(T* addr = 0) : addr(addr), counter(new size_t(1)) {
          TRACE(addr);
        } 
        
        /* copy constructor for Ptr<U> (also covers the case for Ptr<T>) */
        template<typename U>
        Ptr(const Ptr<U>& other)
          : addr((T*)other.addr), counter(other.counter) {
          TRACE(addr);
          ++(*counter);
        }
        
      • Destructor
        ~Ptr() {
          TRACE()
          if (0 == --(*counter)) {
            if (0 != addr) addr->__vptr->__delete(addr);
            delete counter;
          }
        }
        
        • We cannot simply delete addr because Ptr<T> may not have the right type, i.e., represent a static Java superclass, and we thus do not know the correct size of the data layout.
        • We want to use a virtual destructor but we are not allowed to use virtual methods in the translator. We can simulate virtual methods by implementing a method __delete() in every object.
        • We cannot call __delete(Ptr<T>), which calls the copy constructor that increments the counter. This results in repeated calls for __delete() for the same object. So we call __delete(addr)
      • Assignment operator
        Ptr& operator=(const Ptr& right) {
          TRACE(addr);
          if (addr != right.addr) {
            if (0 == --(*counter)) {
              if (0 != addr) addr->__vptr->__delete(addr)
              delete counter;
            }
            addr = right.addr;
            counter = right.counter;
            ++(*counter);
          }
          return *this;
        }
        
      • If the constructor, destructor, and assignment operator are not explicitly defined, the C++ compiler provides a default implementation. Other operators such as != and == need to be explicitly defined if they are needed.
  • Arrays
    • We need a special destructor for arrays
    • Because we separately allocated __data on the heap, only after we delete __data can we delete the data layout of the array.
  • friend
    • We need to access the private fields of smart pointers of different types (e.g., addr and counter). Even though all smart pointers are generated by the same template, they represent different classes. These different classes cannot access their mutual private fields without violating C++ type safety.
    • We can access private data across different template instances of Ptr by declaring all template instances friends of each other:
      template<typename U>
      friend class Ptr;