C++ 11 – std::unique_ptr
C++ introduced smart pointers such as std::unique_ptr, std::shared_ptr, and std::weak_ptr. In this article, we will discuss std::unique_ptr.
std::unique ptr is a smart pointer meaning the memory pointed out of this pointer will automatically be deleted whenever the pointer goes out of scope. The pointer variable would always point to one single object at any time.
It cannot be copied but can be transferred to another std::unique_ptr variable. You can also reset the pointer to point to newly allocated memory; even in this case, previously allocated memory would get deleted.
Let us see a practical C++ example program using all these scenarios. Below is the complete program. It can also be downloaded at https://github.com/codeversionmaster/cplusplus/blob/cplusplus17/unique_ptr.cpp.
$ cat unique_ptr.cpp
#include <iostream>
#include <memory>
int main() {
// 1. Creating and initializing a unique_ptr
std::unique_ptr<int> uptr1(new int(10));
std::cout << "unique_ptr value: " << *uptr1 << std::endl;
// 2. Transferring ownership with std::move
std::unique_ptr<int> uptr2 = std::move(uptr1);
std::cout << "Transferred ownership, unique_ptr2 value: " << *uptr2 << std::endl;
if (!uptr1) std::cout << "Not a valid pointer - uptr1" << std::endl;
// 3. Releasing the managed object and resetting the unique_ptr
int* rawPtr = uptr2.release();
std::cout << "Released uptr2, raw pointer value: " << *rawPtr << std::endl;
// We need to delete rawPtr manually
delete rawPtr;
// 4. When reset to new memory, any older memory if was present would be deleted
// We already released it in this example.
uptr2.reset(new int(20));
std::cout << "Reset unique_ptr2 with new value: " << *uptr2 << std::endl;
// 5. Using a custom deleter with unique_ptr
auto customDeleter = [](int* ptr) {
std::cout << "Custom deleter called for " << *ptr << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(customDeleter)> uptr3(new int(30), customDeleter);
std::cout << "unique_ptr with custom deleter value: " << *uptr3 << std::endl;
std::cout << "program ends here" << std::endl;
// 6. The custom deleter will be called when uptr2 goes out of scope
// 7. Automatically deallocating memory when unique_ptr goes out of scope
return 0;
}
You can compile and run as below and check the output.
$ g++ -o unique_ptr unique_ptr.cpp
$ ./unique_ptr
unique_ptr value: 10
Transferred ownership, unique_ptr2 value: 10
Not a valid pointer - uptr1
Released uptr2, raw pointer value: 10
Reset unique_ptr2 with new value: 20
unique_ptr with custom deleter value: 30
program ends here
Custom deleter called for 30
Let us see step by step what each code snippet means.
You can declare and define std::unique_ptr as shown. Now, uptr1 would point to an integer with the value 10. We call uptr1 the owner of that memory chunk.
Now onwards, uptr1 can point to only this unless released, reset, or transferred.
std::unique_ptr<int> uptr1(new int(10));
std::cout << "unique_ptr value: " << *uptr1 << std::endl;
Here, the ownership of uptr1 is moved to another unique_ptr variable uptr2.
You can see in the output that it prints “Not a valid pointer – uptr1” as we check the pointer uptr1 and do a cout.
std::unique_ptr<int> uptr2 = std::move(uptr1);
std::cout << "Transferred ownership, unique_ptr2 value: " << *uptr2 << std::endl;
if (!uptr1) std::cout << "Not a valid pointer - uptr1" << std::endl;
You can use the release function on uptr2 to release the memory to a raw pointer rawPtr.
As rawPtr is a normal pointer, we need to delete the memory manually.
int* rawPtr = uptr2.release();
std::cout << "Released uptr2, raw pointer value: " << *rawPtr << std::endl;
delete rawPtr;
Using the reset function, we can also reset uptr2 to point to another chunk.
After the reset in the program, uptr2 would point to an integer with the value 20. If it were not released earlier, this step would have deleted the previous memory pointed out by uptr2.
uptr2.reset(new int(20));
std::cout << "Reset unique_ptr2 with new value: " << *uptr2 << std::endl;
Here, we are keeping a custom deleter function that allows specifying any tasks to be performed when the pointer goes out of scope.
The unique_ptr would have automatically deleted when out of scope. But we wrote this custom deleter to show one scenario where it goes out of scope, i.e., when the program reaches the end of the main program.
auto customDeleter = [](int* ptr) {
std::cout << "Custom deleter called for " << *ptr << std::endl;
delete ptr;
};
std::unique_ptr<int, decltype(customDeleter)> uptr3(new int(30), customDeleter);
std::cout << "unique_ptr with custom deleter value: " << *uptr3 << std::endl;
std::cout << "program ends here" << std::endl;
return 0;
What you see here is the portion of output where we can see that the statement “Custom deleter called for 30” is seen after “program ends here” meaning it is called when the pointer went out of scope.
program ends here
Custom deleter called for 30