What is unique_ptr
beginner
c++14
memory
Related: Shared Pointer and The Heap
In C++ std::unique_ptr
(pronounced “unique pointer”) is one of the “smart pointer” classes available in the Standard Library.
What makes std::unique_ptr
“smart” is that it always calls delete
on the pointer it holds onto on destruction as compared to regular pointers, which do not.
std::unique_ptr
implements the dereference operator (*
) and the arrow operator (->
) in order to behave like a pointer even though it’s really an object from a library.
The Standard Library also provides a helper function for creating unique pointers called std::make_unique()
.
std::make_unqiue()
calls new
for you, passing all parameters to the type’s constructor.
This function is useful because objects might throw exceptions during construction and if they do we still need to call delete
on the pointer we received from new
otherwise we will leak the pointer.
std::make_unique()
correctly handles these cases.
This is why it’s considered best practice to always use std::make_unique()
instead of calling new
.
Here’s an example:
#include <memory> #include <iostream> struct Person { Person(std::string name, int age) : name(std::move(name)), age(age) { } ~Person() { std::cout << name << " says bye!\n"; } std::string name; int age; }; void describe_person(const Person& p) { std::cout << p.name << " is " << p.age << "\n"; } int main() { // Best to use std::make_unique auto maria = std::make_unique<Person>("Maria", 40); // You can also pass any pointer to std::unique_ptr's constructor Person* p = new Person("Nushi", 12); std::unique_ptr<Person> nushi(p); // unique_ptr can be used just like regular pointers // because of operator overloading describe_person(*maria); describe_person(*nushi); nushi->age += 1; describe_person(*nushi); }
Maria is 40 Nushi is 12 Nushi is 13 Nushi says bye! Maria says bye!
unique_ptr
Can’t Be Copied
Because std::unique_ptr
calls delete
on the pointer it holds it’s said to own that pointer.
It’s also incorrect to call delete
on the same pointer twice in C++.
To prevent this “double free” from happening we cannot copy unique pointers, but we can instead move them with std::move
.
Moving a unique pointer object moves the ownership of the internal pointer from one to another.
Here’s an example:
#include <memory> #include <iostream> struct Pet { explicit Pet(std::string name) : name(std::move(name)) { } std::string name; }; struct Person { Person(std::string name, int age, std::unique_ptr<Pet> pet) : name(std::move(name)), age(age), pet(std::move(pet)) { } std::string name; int age; std::unique_ptr<Pet> pet; }; void describe_person(const Person& p) { std::cout << p.name << " is " << p.age << "\n"; if (p.pet) { std::cout << p.name << " has a pet named " << p.pet->name << "\n"; } } int main() { auto scout = std::make_unique<Pet>("Scout"); auto nushi = std::make_unique<Person>("Nushi", 12, std::move(scout)); describe_person(*nushi); }
Nushi is 12 Nushi has a pet named Scout
get()
Sometimes an API demands a pointer be passed as a parameter.
std::unique_ptr
provides a get()
method for just such a purpose.
get()
returns the underlying pointer, but be careful, the ownership of the pointer still belongs to the std::unique_ptr
object.
#include <memory> #include <iostream> struct Pet { explicit Pet(std::string name) : name(std::move(name)) { } std::string name; }; struct Person { Person(std::string name, int age, std::unique_ptr<Pet> pet) : name(std::move(name)), age(age), pet(std::move(pet)) { } std::string name; int age; std::unique_ptr<Pet> pet; }; void describe_person(Person* p) { std::cout << p->name << " is " << p->age << "\n"; if (p->pet) { std::cout << p->name << " has a pet named " << p->pet->name << "\n"; } } int main() { auto scout = std::make_unique<Pet>("Scout"); auto nushi = std::make_unique<Person>("Nushi", 12, std::move(scout)); describe_person(nushi.get()); }
Nushi is 12 Nushi has a pet named Scout