Smart Pointer

Unique Pointer (unique_ptr)

Introduced since C++11, included in the <memory> library. Learned formally in CS247.

Use cases for unique_ptr

unique_ptr are good for representing ownership (owns-a), since when one object dies, its unique_ptr fields will run and clean up the associated object.

If you want a has-a relationship, use raw pointers or references instead.

Usage

std::unique_ptr<B> p = std::make_unique<B>(...); 

Links

You can access the underlying raw pointer of a smart pointer via .get().

Motivation

In CS247, the teacher motivated the use of smart pointers after introducing Exceptions. In particular, consider the following function:

void f() {
	MyClass m;
	MyClass* p = new MyClass{};
	g();
	delete p;
}
  • Under normal circumstances, f does not leak memory. But, if g throws, then we do not execute delete p, so we do leak memory!

if we were to fix f with a try catch block, we could have something like this:

void f() {
	MyClass m;
	MyClass* p = new MyClass{};
	try {
		g();
	} catch (...) {
		delete p;
		throw;
	}
	delete p;
}

One solution: wrap the pointer in a stack allocated object that will delete it for us during stack unwinding, which is exactly what a unique_ptr does!

Applying RAII to dynamic memory gives us std::unique_ptr<T> (in <memory> library)

  • contains a T* passed via the constructor
  • deletes the T* in the destructor
void f() {
	MyClass m:
	std::unique_ptr<MyClass> p{new MyClass{}};
	g();
}

If we leave f normally, or if we leave f during stack unwinding, either way, p’s destructor runs, deletes heap memory (because of implement of unique_ptr).

In between, we can use p like a regular pointer thanks to operator overload.

CAREFUL

Do NOT create unique_ptr with a pointer

Generally, we do not call the constructor directly with a pointer, because of several issues (more below).

The issues:

  1. std::unique_ptr<T> p{new T{}}; new is not paired with a delete (not one that we see, at least)
  2. Causes a double delete:
T* p = new T{};
std::unique_ptr<T> w{p};
std::unique_ptr<T> r{p};
  1. g() can potentially throw in the example below, heap-allocated object does not get deleted
f(std::unique_ptr<T> {new T()}, g());

One potential ordering (in C++14) (obscure scenario)

  1. new T()
  2. g()
  3. unique_ptr constructor
  4. f()

Preferred alternative: std::make_unique<T> (...)

This constructs a T object in the heap with arguments (…), and returns a unique_ptr<T> pointing at this object.

I saw something like the PImpl Idiom, in that case they are using the tructor to construct the new unique pointer.

BE CAREFUL with Syntax

Misremembering the syntax, it can be easy to make a mistake like this:

auto s = make_unique<Sword*>(); //Compiles, but BAD
  • Notice the *. This actually is a unique pointer to a pointer. is this valid syntax? How does it get constructed? make_unique<Sword*>() will allocate memory for a raw pointer to Sword on the heap.

I think the confusion came from learning about static_cast, in which case we do include the * in the <> template arguments.

More Food for Thought

Something else to consider:

unique_ptr<T> p = make_unique<T>(...);
unique_ptr<T> q = p;

Call copy constructor to copy p into q.

What happens? Doesn’t compile. Copying is disabled for unique_ptrs (achieved with = delete keyword), they can only be moved.

unique_ptr vs. object fields for owns-a relationship

Which one should you use, since both represent owns-a relationship?

Object Fields do not enable you to have Polymorphism. If you use object references, then it isn’t a owns-a relationship anymore. On the other hand, with unique_ptr, you are NOT allowed to copy construct.

A unique_ptr can only be moved.

Pass by Value vs. Pass by Reference for Unique Pointers

What is the different between unique_ptr<Level> and unique_ptr<level>&?

Ran into this while working on my Biquadris project.

  • unique_ptr<Level> owns and manages the pointer to an object of type Level. When the unique_ptr goes out of scope, the object it points to is automatically deleted.
  • unique_ptr<Level>& is a reference to a unique_ptr that manages a Level object. It does not own the object; it simply refers to an existing unique_ptr.

So it’s about ownership. Remember that Object Destruction happens when the object gets out of scope. This does NOT happen for References.

So in general, you probably always want to pass the pointer by reference, UNLESS you are trying to transfer ownership.

Passing by Value (Ownership Transfer):

void takeOwnership(std::unique_ptr<int> ptr) { /*...*/ } std::unique_ptr<int> myPtr = std::make_unique<int>(42); takeOwnership(std::move(myPtr)); // Ownership transferred

Passing by Reference (Access without Ownership Transfer):

void accessWithoutOwnership(const std::unique_ptr<int>& ptr) { /*...*/ }
std::unique_ptr<int> myPtr = std::make_unique<int>(42);
accessWithoutOwnership(myPtr); // Ownership retained by myPtr

Under the Hood, how is unique_ptr implemented?

This is code provided by Ross Evans, professor of CS247. You should try to reason and come up with your own implementation.

#include <utility>
#include <iostream>
 
template <typename T> class unique_ptr {
  T *ptr;
 public:
  unique_ptr(T *p): ptr{p} {}
  ~unique_ptr() { delete ptr; }
 
  unique_ptr(const unique_ptr<T> &other) = delete;
  unique_ptr<T> &operator=(const unique_ptr<T> &other) = delete;
 
  unique_ptr(unique_ptr<T> &&other): ptr{other.ptr} { other.ptr = nullptr; }
  unique_ptr<T> &operator=(unique_ptr<T> &&other) {
    using std::swap;
    swap(ptr, other.ptr);
    return *this;
  }
 
  T &operator*() { return *ptr; }
 
  T *operator->() { return ptr; }
};
 
class C {
  int x;
 public:
  C (int x): x {x} { std::cout << "Ctor running with x = " << x << std::endl; }
  ~C() { std::cout << "Dtor running." << std::endl; }
};
 
int main() {
  unique_ptr<C> p{new C{10}};
}