Skip to content

🎯 unique_ptr — Exclusive Ownership

std::unique_ptrlựa chọn mặc định cho smart pointers. Một object chỉ có một chủ sở hữu duy nhất.

Analogy: Chìa khóa duy nhất

┌─────────────────────────────────────────────────────────────────┐
│                    UNIQUE KEY ANALOGY                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   unique_ptr = Chìa khóa duy nhất của căn phòng                │
│                                                                 │
│   • Chỉ CÓ 1 người giữ chìa → Chỉ 1 owner                      │
│   • Muốn đưa cho người khác → CHUYỂN chìa (move)              │
│   • Không thể copy chìa → Không thể copy unique_ptr            │
│   • Người giữ chìa đi → Phòng tự động khóa (destructor)       │
│                                                                 │
│   ┌─────────┐    move     ┌─────────┐                          │
│   │ Owner A │ ──────────► │ Owner B │                          │
│   │  🔑     │             │  🔑     │                          │
│   │         │   (A no     │         │                          │
│   │  [empty]│   longer    │  [owns] │                          │
│   └─────────┘   owns)     └─────────┘                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Basic Usage

cpp
#include <memory>

// ✅ Create with make_unique (C++14)
auto ptr = std::make_unique<int>(42);

// ✅ Access value
std::cout << *ptr << "\n";  // 42

// ✅ Auto-deleted when ptr goes out of scope

Creating unique_ptr

cpp
#include <memory>

// Method 1: make_unique (PREFERRED)
auto p1 = std::make_unique<int>(42);
auto p2 = std::make_unique<std::string>("Hello");
auto p3 = std::make_unique<int[]>(10);  // Array of 10 ints

// Method 2: Constructor (less safe)
std::unique_ptr<int> p4(new int(42));

// ❌ NEVER do this (not exception-safe)
func(std::unique_ptr<A>(new A()), std::unique_ptr<B>(new B()));
// If new B() throws, new A() leaks!

// ✅ Safe version
func(std::make_unique<A>(), std::make_unique<B>());

Ownership Transfer (Move)

cpp
#include <memory>

auto ptr1 = std::make_unique<int>(42);

// ❌ Copy not allowed
// auto ptr2 = ptr1;  // Compile error!

// ✅ Move ownership
auto ptr2 = std::move(ptr1);

// ptr1 is now nullptr!
if (!ptr1) {
    std::cout << "ptr1 is empty\n";
}

// ptr2 owns the resource
std::cout << *ptr2 << "\n";  // 42

Accessing the Object

cpp
auto ptr = std::make_unique<std::string>("Hello");

// Dereference
std::cout << *ptr << "\n";  // Hello

// Access member
std::cout << ptr->size() << "\n";  // 5

// Get raw pointer (non-owning)
std::string* raw = ptr.get();

// Check if valid
if (ptr) {
    std::cout << "ptr is valid\n";
}

Common Operations

cpp
auto ptr = std::make_unique<int>(42);

// Release ownership (returns raw pointer)
int* raw = ptr.release();
// ptr is now nullptr, YOU own raw and must delete it!
delete raw;

// Reset to new value
ptr.reset(new int(100));  // Old value deleted, now points to 100

// Reset to nullptr
ptr.reset();  // Deletes and becomes nullptr

// Swap
auto ptr2 = std::make_unique<int>(200);
ptr.swap(ptr2);

unique_ptr with Classes

cpp
class Widget {
    int id_;
public:
    explicit Widget(int id) : id_(id) {
        std::cout << "Widget " << id_ << " created\n";
    }
    ~Widget() {
        std::cout << "Widget " << id_ << " destroyed\n";
    }
    void doWork() { std::cout << "Working...\n"; }
};

int main() {
    auto widget = std::make_unique<Widget>(1);
    widget->doWork();
    
    // Widget 1 destroyed when main() exits
}

Output:

Widget 1 created
Working...
Widget 1 destroyed

Passing unique_ptr

cpp
// Transfer ownership INTO function
void takeOwnership(std::unique_ptr<Widget> w) {
    w->doWork();
}  // w destroyed here

// Return ownership FROM function
std::unique_ptr<Widget> createWidget(int id) {
    return std::make_unique<Widget>(id);
}

// Pass by reference (non-owning access)
void useWidget(const std::unique_ptr<Widget>& w) {
    w->doWork();  // Don't take ownership
}

// Better: Just pass raw pointer for non-owning
void useWidgetBetter(Widget* w) {
    if (w) w->doWork();
}

int main() {
    auto w = createWidget(1);
    useWidget(w);
    useWidgetBetter(w.get());
    takeOwnership(std::move(w));  // Must move!
    // w is now nullptr
}

unique_ptr with Arrays

cpp
// Array version
auto arr = std::make_unique<int[]>(100);

arr[0] = 1;
arr[99] = 100;

// ✅ Auto-calls delete[] (not delete)

// For dynamic arrays, prefer std::vector though!
std::vector<int> vec(100);  // Usually better

Custom Deleter

cpp
// For resources that need special cleanup
struct FileDeleter {
    void operator()(FILE* file) const {
        if (file) fclose(file);
    }
};

std::unique_ptr<FILE, FileDeleter> openFile(const char* path) {
    return std::unique_ptr<FILE, FileDeleter>(
        fopen(path, "r")
    );
}

// Lambda deleter
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
    fopen("data.txt", "r"),
    &fclose
);

unique_ptr vs Raw Pointer

AspectRaw Pointerunique_ptr
OwnershipUnclearClear (exclusive)
CleanupManual deleteAutomatic
CopyDanger!Compile error
MoveManualBuilt-in
OverheadNoneNone (zero-cost)!

📚 Tổng kết

FeatureDescription
OwnershipExclusive (single owner)
Copy❌ Disabled
Move✅ Supported
OverheadZero (same as raw pointer)
Default choice✅ Yes!

➡️ Tiếp theo

Khi cần nhiều owners cùng sở hữu 1 object:

shared_ptr → — Shared ownership.