Exclusive Ownership with std::unique_ptr
Frequently Asked Questions
QWhat happens if I dereference a unique_ptr that has been moved from?
QCan unique_ptr manage arrays?
QWhat is the difference between release() and reset()?
QHow should I pass a unique_ptr to a function that does not take ownership?
#include <iostream>
#include <memory>
#include <format>
#include <cstdio>Helper class -- a resource that logs its lifetime
class Resource {
std::string name_;
public:
explicit Resource(std::string name) : name_(std::move(name)) {
std::cout << std::format("Resource '{}' acquired\n", name_);
}
~Resource() {
std::cout << std::format("Resource '{}' released\n", name_);
}
void use() const {
std::cout << std::format("Using resource '{}'\n", name_);
}
};HOW UNIQUE_PTR WORKS INTERNALLY Deep Dive
Watch out: a stateful deleter (one that stores data) increases sizeof(unique_ptr) beyond sizeof(T*), because the deleter is stored inside the unique_ptr object itself (via empty-base optimisation for stateless deleters like function pointers). -----------------------------------------------
1 Transferring ownership to a function
Attempting to copy is a compile error.
Use this when it cleanly solves the problem in front of you.
transfer ownership.
Use it in code like: void transfer_ownership(std::unique_ptr<Resource> res) {.
transfer ownership. Attempting to copy is a compile error.
Watch out: unique_ptr cannot be copied -- use std::move() to
void transfer_ownership(std::unique_ptr<Resource> res) {
res->use();
// res is automatically deleted when function exits
}2 Returning unique_ptr from a factory function
-- it returns the raw pointer and relinquishes ownership, causing a leak if the return value is not captured.
-- it returns the raw pointer and relinquishes ownership, causing a leak if the return value is not captured.
-- it returns the raw pointer and relinquishes ownership, causing a leak if the return value is not captured.
Follow the code pattern in this section and keep usage explicit.
-- it returns the raw pointer and relinquishes ownership, causing a leak if the return value is not captured.
Watch out: never call .release() without assigning the result
HOW MAKE_UNIQUE IS EXCEPTION-SAFE Deep Dive
std::unique_ptr<Resource> create_resource(const std::string& name) {
// No std::move needed -- the compiler applies copy elision / implicit move
return std::make_unique<Resource>(name);
}3 Custom deleter example
Providing a custom deleter to unique_ptr so it can manage non-new resources like C-style FILE* handles.
When wrapping C library resources (FILE*, sqlite3*, HANDLE) that require a specific cleanup function instead of delete.
It improves clarity and helps prevent common correctness mistakes.
Follow the code pattern shown in this section and adapt it to your types.
unique_ptr can manage any resource -- not just heap objects -- by providing a custom deleter. Here we wrap a C-style FILE* so that fclose is called automatically when the unique_ptr goes out of scope.
void demonstrate_custom_deleter() {
std::cout << "\n--- Custom deleter (FILE*) ---\n";
// Lambda deleter: closes the file and logs
auto file_deleter = [](FILE* fp) {
if (fp) {
std::cout << std::format("Custom deleter: closing FILE*\n");
std::fclose(fp);
}
};
// unique_ptr<FILE, decltype(lambda)> -- the deleter type is part of
// the unique_ptr type. Because the lambda is stateless (captures
// nothing), the empty-base optimisation keeps sizeof == sizeof(FILE*).
{
auto file = std::unique_ptr<FILE, decltype(file_deleter)>(
std::fopen("unique_ptr_demo.txt", "w"), file_deleter);
if (file) {
std::fputs("Hello from unique_ptr with custom deleter!\n", file.get());
std::cout << std::format("Wrote to file via unique_ptr<FILE*>\n");
}
// file goes out of scope here -> file_deleter is called -> fclose
}
std::cout << "File automatically closed by custom deleter\n";
}int main() {
// Create with make_unique (preferred)
auto res1 = std::make_unique<Resource>("Database");
res1->use();
// Transfer ownership (move, not copy)
auto res2 = std::move(res1);
// res1 is now nullptr!
if (!res1) {
std::cout << "res1 is now empty after move\n";
}
// Pass ownership to function
transfer_ownership(std::move(res2));
// Factory function returning unique_ptr
auto res3 = create_resource("Network");
res3->use();
// res3 cleaned up automatically at end of scope
// Custom deleter demonstration
demonstrate_custom_deleter();
std::cout << "Back in main - resources auto-cleaned!\n";
return 0;
}