Introduction
When dynamic memory is allocated using new in C++, it must always be manually released using delete. Forgetting to do so causes memory leaks.
To solve this problem, C++11 introduced std::unique_ptr, a smart pointer that automates object lifetime management based on the RAII (Resource Acquisition Is Initialization) principle.
- Acquisition: It takes ownership of memory (resource) when the
unique_ptris created. - Release: When the
unique_ptrgoes out of scope and is destroyed, its destructor automatically callsdeleteto release the resource.
In this article, I will explain three main uses of unique_ptr:
- Basic Usage (Automatic Memory Release)
- Moving Ownership (
std::move) - Specifying Custom Deleters
Prerequisite: What is C++11?
C++11 is a major update to the C++ language formalized in 2011. Since unique_ptr was introduced in C++11, you need a compiler that supports C++11 or later to use it.
1. Basic Usage
A pointer wrapped in unique_ptr is automatically deleted when the unique_ptr variable goes out of scope.
Sample Code
#include <iostream>
#include <memory> // unique_ptr, make_unique
#include <string>
using namespace std;
struct Resource {
string name;
Resource(string n) : name(n) { cout << "Resource '" << name << "' acquired" << endl; }
~Resource() { cout << "Resource '" << name << "' released" << endl; }
void use() { cout << "Resource '" << name << "' is being used..." << endl; }
};
void basic_usage_example() {
cout << "--- Entered function ---" << endl;
// Use make_unique (C++14) to safely create a unique_ptr
unique_ptr<Resource> p_res = make_unique<Resource>("Data A");
// Access members with -> just like a normal pointer
p_res->use();
cout << "--- Exiting function ---" << endl;
// p_res goes out of scope here, so the destructor is called,
// and the Resource object is automatically deleted.
}
int main() {
basic_usage_example();
return 0;
}
2. Moving Ownership (std::move)
As the name implies, unique_ptr enforces unique (sole) ownership, so copying it is strictly prohibited. If you want to transfer the ownership held by one unique_ptr to another, you must use std::move.
Sample Code
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Resource { /* ... */ };
int main() {
unique_ptr<Resource> p1 = make_unique<Resource>();
// NG: Copying results in a compilation error!
// unique_ptr<Resource> p2 = p1;
// OK: Use std::move to "move" ownership from p1 to p2
unique_ptr<Resource> p2 = move(p1);
// p1 has lost ownership and now points to nothing (nullptr)
if (!p1) {
cout << "p1 has lost ownership." << endl;
}
// It is also possible to move ownership into a vector
vector<unique_ptr<Resource>> vec;
vec.push_back(move(p2));
return 0;
}
Explanation: After performing move, the original pointers (p1 or p2) become empty and no longer manage the resource. Ownership is always held by only one unique_ptr.
3. Specifying Custom Deleters
unique_ptr allows you to specify a custom deleter for cases where resources need to be released by methods other than delete (e.g., closing a file opened with fopen using fclose).
Sample Code
#include <iostream>
#include <memory>
#include <cstdio> // FILE, fopen, fclose, fwrite
using namespace std;
// 1. Custom deleter function to fclose FILE*
void file_closer(FILE* fp) {
if (fp) {
fclose(fp);
cout << "Custom Deleter: File closed." << endl;
}
}
int main() {
// 2. Specify the deleter type in the second template argument of unique_ptr
unique_ptr<FILE, decltype(&file_closer)> file_ptr(
fopen("test.txt", "w"), // Acquire resource
file_closer // Pass the function used for release
);
if (file_ptr) {
fwrite("hello", 1, 5, file_ptr.get());
}
// When exiting the main function, file_ptr's destructor
// automatically calls file_closer(fp).
return 0;
}
Explanation: By passing the type of the deleter (decltype(&file_closer)) as the second template argument of unique_ptr and the deleter function itself as the second argument of the constructor, that function will be called instead of delete when releasing the resource.
Summary
In this article, I explained the basics of safe dynamic memory management using C++11 std::unique_ptr.
unique_ptrrestricts resource ownership to a single entity.- It automatically releases resources when going out of scope, eliminating the risk of forgetting to
delete. - Ownership cannot be copied; it must be moved using
std::move. - By specifying a custom deleter, it can handle release processes other than
delete.
In situations where new is used, using unique_ptr (or shared_ptr) is generally the most basic and powerful way to prevent memory leaks in modern C++.
