C++ Smart Pointer

29 Dec 2021

This post is my notes while going through [1]. see Sources section for all sources used in this poist. I do not own any of the materials.

Background

Smart Pointers

unique_ptr

Example: what is the output of the following?

struct MyInt {
  MyInt(int i) : i_(i) {}
  ~MyInt() {
    std::cout << "Good bye from " << i_ << std::endl;
  }
  int i_;
};

int main() {

  std::cout << std::endl;

  MyInt* myInt15 = new MyInt(15);
  std::unique_ptr<MyInt> uniquePtr1(myInt15);
  std::unique_ptr<MyInt> uniquePtr15(myInt15);

  std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl;

  std::unique_ptr<MyInt> uniquePtr2{std::move(uniquePtr1)};
  std::cout << "uniquePtr1.get(): " << uniquePtr1.get() << std::endl;
  std::cout << "uniquePtr2.get(): " << uniquePtr2.get() << std::endl;

  std::cout << std::endl;


  {
    std::unique_ptr<MyInt> localPtr{new MyInt(2003)};
  }

  std::cout << std::endl;

  // destroy what uniquePtr2 was holding and take ownership of new ptr
  // think of new as malloc: returns pointer to heap
  uniquePtr2.reset(new MyInt(2011));
  // uniquePtr2 not responsible for 2011 anymore. Returns value of stored ptr
  MyInt* myInt= uniquePtr2.release();
  delete myInt;

  std::cout << std::endl;

}
// let the value of myInt15 be XXX
uniquePtr1.get(): XXX
uniquePtr1.get(): 0
uniquePtr2.get(): XXX

Good bye from 2003
Good bye from 15
Good bye from 2011

Shared Pointer

Ex. what is the output?

class MyInt {
public:
  MyInt(int v) : val(v) {
   std::cout << "  Hello: " << val << std::endl;
  }
  ~MyInt() {
   std::cout << "  Good Bye: " << val << std::endl;
  }
private:
  int val;
};

int main() {

  std::cout << std::endl;

  std::shared_ptr<MyInt> sharPtr(new MyInt(1998));
  
  std::cout << "sharedPtr.use_count(): " << sharPtr.use_count() << std::endl;
  {
    // copy the shared ptr. now two owners
    std::shared_ptr<MyInt> locSharPtr(sharPtr);
    std::cout << "locSharPtr.use_count(): " << locSharPtr.use_count() << std::endl;
  }
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;

  std::shared_ptr<MyInt> globSharPtr = sharPtr; // copy assignment
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;
  
  globSharPtr.reset(); //This only release one owner
  std::cout << "sharPtr.use_count(): "<<  sharPtr.use_count() << std::endl;

  // make sharPtr take care of a different resource.
  sharPtr = std::shared_ptr<MyInt>(new MyInt(2011));

  std::cout << std::endl;
}

Solution:

Hello: 1998
sharedPtr.use_count(): 1
locSharedPtr.use_count(): 2
sharedPtr.use_count(): 1
sharedPtr.use_count(): 2
sharedPtr.use_count(): 1
Hello: 2011
Good Bye: 1998
Good Bye: 2011

Custom deleter example:

template <typename T>
class Deleter {
public:
  // Key!!! this is called by the shared pointer to release the resource
  void operator()(T *ptr) {
    ++Deleter::count;
    delete ptr;
  }
  void getInfo() {
    std::string typeId{typeid(T).name()};
    size_t sz= Deleter::count * sizeof(T);
    std::cout << "Deleted " << Deleter::count << " objects of type: " << typeId << std::endl;
  }
private:
  static int count;  // note that this is static: shared by all instances of Deleter
};

template <typename T>
int Deleter<T>::count = 0;
typedef Deleter<int> IntDeleter;

int main() {
  int* myint = new int(1998);
  std::shared_ptr<int> sharedPtr1(myint, IntDeleter());
  std::shared_ptr<int> sharedPtr2 = sharedPtr1; // ref count ++ 
  std::cout << "sharPtr1.use_count(): "<<  sharedPtr1.use_count() << std::endl;

  auto intDeleter= std::get_deleter<IntDeleter>(sharedPtr1);
  intDeleter->getInfo();
  sharedPtr2.reset(); // this does not delete stuff since the underlying resource is not destroyed
  intDeleter->getInfo();
  std::cout << "sharPtr1.use_count(): "<<  sharedPtr1.use_count() << std::endl;
  sharedPtr1.reset(); // this should delete stuff since ref count is now 0
  intDeleter->getInfo();
}

Output:

sharPtr1.use_count(): 2
Deleted 0 objects of type: i
sharPtr1.use_count(): 1
Deleted 0 objects of type: i
Deleted 1 objects of type: i

Weak Pointer

struct B;
struct A {
  // each struct contains a shared ptr as a member
  std::shared_ptr<B> b;  
  ~A() { std::cout << "~A()\n"; }
};

struct B {
  std::shared_ptr<A> a;
  ~B() { std::cout << "~B()\n"; }  
};

void useAnB() {
  // make_shared passes the provided arguments (empty here) to type T's constructor (A).
  // since we are using default constructor, empty arguments.
  auto a_shared = std::make_shared<A>();
  auto b_shared = std::make_shared<B>();
  // we have 4 shared_ptr here: struct A's b, struct B's a, a_shared, and b_shared
  a_shared->b = b_shared;
  b_shared->a = a_shared;
  // a_shared and b_shared out of scope. But no destructors are called!
  // Although a_shared and b_shared are destroyed, the structs themselves are not because 
  // two shared pointers still exist in the program!
  // btw, make_shared "uses ::new to allocate storage for the object", so memory leak
}

int main() {
   useAnB();
   std::cout << "Finished using A and B\n";
}

// the solution is to make either one of A->b or B->a a weak pointer to break the cycle.

  auto sharedPtr = std::make_shared<int>(2011);
  // create weak pointer from shared pointer
  std::weak_ptr<int> weakPtr(sharedPtr);
  
  // this use_count returns the number of shared_ptrs owning the obj. 
  // it really has nothing to do with the weak pointer itself.
  std::cout << "weakPtr.use_count(): " << weakPtr.use_count() << std::endl;
  std::cout << "sharedPtr.use_count(): " << sharedPtr.use_count() << std::endl;
  // expired() checks whether the referenced obj is already deleted
  std::cout << "weakPtr.expired(): " << weakPtr.expired() << std::endl;

  // create a temporary shared ptr to access object
  if( std::shared_ptr<int> sharedPtr1 = weakPtr.lock() ) {
    std::cout << "*sharedPtr: " << *sharedPtr << std::endl;
    // the temporary shared pointer still counts
    std::cout << "sharedPtr.use_count(): " << sharedPtr.use_count() << std::endl;
  } else {
    std::cout << "Don't get the resource!" << std::endl;
  }

  // release the referece. Note that this does not change the object and its shared pointers.
  weakPtr.reset(); 
  if( std::shared_ptr<int> sharedPtr1 = weakPtr.lock() ) {
    std::cout << "*sharedPtr: " << *sharedPtr << std::endl;
  } else {
    std::cout << "Don't get the resource!" << std::endl;
  }

Output:

  weakPtr.use_count(): 1
  sharedPtr.use_count(): 1
  weakPtr.expired(): 0
  *sharedPtr: 2011
  sharedPtr.use_count(): 2
  Don't get the resource!

Sources

[1] https://www.youtube.com/watch?v=sQCSX7vmmKY&list=PLHTh1InhhwT5o3GwbFYy3sR7HDNRA353e&index=10

[2] https://www.cplusplus.com/reference/memory/unique_ptr/

[3] https://stackoverflow.com/questions/49334967/c-multiple-unique-pointers-from-same-raw-pointer

[4] https://stackoverflow.com/questions/27085782/how-to-break-shared-ptr-cyclic-reference-using-weak-ptr