Introduction
Unique pointers are smart pointers that help us in memory management. Unique pointers guarantee they delete their object if they are destructed, therefore, one less hassle for programmers. They follow “Resource Acquisition Is Initialization” (RAII) rule.
Prerequisites
Here, I assume you are aware of raw pointers and auto keyword.
I compiled the examples using GCC 10.2 with flag -std=c++20
.
To be focused, the headers and main()
are omitted in some examples:
#include <iostream> // For std::cout
#include <memory> // For std::unique_ptr, std::make_unique
using namespace std; // dropping std::
// class definitions
int main(){
// implementations
return 0;
}
Unique pointer
A unique pointer is defined as
std::unique_ptr<int> p(new int); // p is allocated a new int on the heap
If the unique pointer is destructed, the allocated object on the heap is destructed too
{
unique_ptr<int> p(new int);
// make use of p
} // p is destructed, so the int object is destructed.
Compare the above code with raw pointers which are deleted explicitly by programmers.
A unique pointer can also be created with std::make_unique
#include <memory>
#include <iostream>
using namespace std;
struct Person{
Person(string n):Name(n){}
~Person(){cout<<"Deleted!";}
string Name;
};
int main(){
auto p = make_unique<Person>("Jack"); // Person constructor is called.
return 0;
}
Ownership
A unique pointer is a 1-to-1 relationship between a pointer (p
) and its allocated object on the heap (new int
).
unique_ptr<int> p(new int);
// p <--------> object
p
owns the object and the object has only one owner, p
. So when programming, we can think of them as one entity.
A unique pointer cannot be copied or passed by value. However, the ownership of its object can be transferred.
A unique pointer can be empty too
unique_ptr<int> p; // empty pointer, contains null pointer
Operations
A unique pointer supports operations below
struct Person{
Person(string n):Name(n){}
string Name;
};
auto p = make_unique<Person>("Rose"); // create unique pointer
auto b = *p; // dereference pointer
p->Name = "Jack"; // access class members
There is a raw pointer inside a unique pointer that can be accessed:
auto r = p.get(); // get the raw pointer
Use the above raw pointer only for calculations and do not delete it as it is managed by a unique pointer.
The object allocated to the pointer can be changed but remember that it is automatically deleted:
#include<iostream>
#include<memory>
using namespace std;
int main(){
auto q = make_unique<int>();
cout<< q <<'\n'; // points to 0xb1feb0
q = make_unique<int>(); // The data in 0xb1feb0 deleted.
cout<<q; //points to new object in 0xb20ee0
return 0;
}
The pointer can be reset to a new object
auto q = make_unique<int>(); // q created with an int object on the heap
q.reset(new int()); // The previouse object deleted, a new one is created.
Only one unique pointer owns the object on the heap:
auto q = make_unique<int>(); // q associate to a newly created object on the heap
auto p = q; // Error: the object belongs to q and cannot be shared.
However, the ownership of the object can be transfered via std::move()
:
auto q = make_unique<int>(); // q created with an int object on the heap
auto p = move(q); // p owns the q's object, q lost it (null pointer).
std::swap
works with unique pointers
auto p = make_unique<int>();
auto q = make_unique<int>();
swap(p,q); // p points to q's object and vice versa.
A unique pointer can be checked if it is associated with an object
unique_ptr<int> a; // a created but is empty (null pointer)
if (a) // (bool) a returns false as it is not associated.
cout<<*a; // This is not run.
Pass to a function
The function below takes the ownership of a unique pointer. To pass the pointer std::move()
must be used:
#include<iostream>
#include<memory>
using namespace std;
struct A{
~A(){cout<<"Deleted.";}
};
void PassIn(std::unique_ptr<A> a)
{
cout<< "Pointer received."<<'\n';
} // a and its object are deleted.
int main(){
auto x = make_unique<A>();
PassIn(move(x)) // Pointer received.
; // Deleted.
if (!x) cout<< "x is empty."; // true: x is empty.
return 0;
}
Return from function
A function can return a unique pointer. Consequently, it gives up the ownership of the pointer:
#include<iostream>
#include<memory>
using namespace std;
struct A{};
std::unique_ptr<A> PassOut()
{
auto a = make_unique<A>();
return a;
}
int main(){
auto x = PassOut();
if (x) cout<< "x has an object."; // true: x has an object.
return 0;
}
Pass to observer function
If the function only works with the pointer’s object and doesn’t care about the ownership, we can pass the unique pointer by a reference or raw pointer. If null pointer should be handled, we pass it by a raw pointer:
#include<iostream>
#include<memory>
using namespace std;
struct Database{
double GetAverageSalary(){return 1000;};
};
void ShowSalaryDifference(double salary, Database* db)
{
if (!db) throw runtime_error("Database is null.");
cout<< salary - db->GetAverageSalary();
}
int main(){
auto db = make_unique<Database>();
ShowSalaryDifference(1200, db.get()); // 200
return 0;
}
If we are sure db
has an object, we can pass it by reference
void ShowSalaryDifference(double salary, Database& db)
{
cout<< salary - db.GetAverageSalary();
}
int main(){
auto db = make_unique<Database>();
ShowSalaryDifference(1200,*db); // 200
return 0;
}
Class member: unique pointer vs raw pointer vs reference
If we design our program based on smart pointers, we can assume the below rules for a class member:
- Unique pointer member: the class is the owner of the pointer’s object.
- Raw pointer member: the class is an observer and not responsible for deleting the pointer’s object. It is deleted by a smart pointer outside of this class. The pointer can be null.
- Reference member: it is guaranteed that the reference contains valid data while the class object is alive.
Performance
Accessing unique pointers is as fast as raw pointers. The class of the unique pointer contains only a raw pointer as the data member, so, the size of a unique pointer is the same as a raw pointer. All in all, unique pointers can safely replace raw pointers in high-performance calculations.
Factory Example
A factory that creates unique pointers is shown below
#include<iostream>
#include<memory>
using namespace std;
struct Base{};
struct Derived:Base{};
std::unique_ptr<Base> create(int option)
{
if (option == 0)
return make_unique<Base>();
else if (option==1)
return make_unique<Derived>();
else
throw runtime_error("Wrong option.");
}
int main(){
auto p = create(1);
return 0;
}
Next study
I recommend after this have a look at my posts on shared pointers and weak pointers.
References
unique pointer from cppreference
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks