std::unique_ptr

std::unique_ptr in C++: An Overview

std::unique_ptr is a smart pointer introduced in C++11 that provides automatic and exclusive ownership of a dynamically allocated object. It resides in the <memory> header and is part of the Standard Template Library (STL). Below is a comprehensive explanation of what std::unique_ptr is, what it does, and its features.


Purpose of std::unique_ptr

  1. Automatic Memory Management: std::unique_ptr manages the lifetime of a dynamically allocated object. When the std::unique_ptr goes out of scope, the object it owns is automatically deleted.

  2. Exclusive Ownership: A std::unique_ptr owns the object exclusively. No other std::unique_ptr can own the same object unless ownership is explicitly transferred.


Key Features of std::unique_ptr

1. Exclusive Ownership

  • A std::unique_ptr can only point to one dynamically allocated object at a time.

  • It cannot be copied, but ownership can be transferred using std::move.

std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
// std::unique_ptr<int> ptr2 = ptr1; // Error: copy not allowed
std::unique_ptr<int> ptr2 = std::move(ptr1); // Ownership transferred

2. Automatic Deletion

  • When the std::unique_ptr is destroyed or reassigned, it automatically deletes the object it owns.

  • This helps prevent memory leaks, as you don't need to explicitly delete the object.

{
    std::unique_ptr<int> ptr = std::make_unique<int>(42);
    // The integer will be automatically deleted when 'ptr' goes out of scope.
}

3. Null State

  • A std::unique_ptr can be null, indicating that it doesn’t own any object. This is the default state after initialization or ownership transfer.

std::unique_ptr<int> ptr;
if (!ptr) {
    std::cout << "ptr is null." << std::endl;
}

4. Custom Deleters

  • std::unique_ptr supports custom deleters for specialized cleanup. You can specify a callable object (like a function pointer or lambda) to define how the object should be deleted.

struct CustomDeleter {
    void operator()(int* p) const {
        std::cout << "Deleting " << *p << std::endl;
        delete p;
    }
};

std::unique_ptr<int, CustomDeleter> ptr(new int(42));

5. Compatibility with Arrays

  • A specialization of std::unique_ptr (std::unique_ptr<T[]>) is available to manage dynamic arrays.

std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
for (int i = 0; i < 5; ++i) arr[i] = i;

6. Non-Copyable, Move-Only

  • Copying a std::unique_ptr is prohibited, enforcing the single-ownership principle.

  • Ownership can be transferred using std::move.

std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1); // Ownership transferred

7. Lightweight

  • Since std::unique_ptr does not use reference counting (unlike std::shared_ptr), it is lightweight and incurs minimal overhead.


8. Efficient Memory Allocation

  • Using std::make_unique (introduced in C++14), you can create a std::unique_ptr with a single allocation. This is safer and more efficient than creating an object with new.

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

Common Use Cases

  1. Managing Resources in RAII (Resource Acquisition Is Initialization): std::unique_ptr ensures that resources are cleaned up when no longer needed.

  2. Preventing Memory Leaks: Eliminates the need for manual delete calls.

  3. Dynamic Object Ownership Transfer: Allows controlled transfer of ownership between different parts of the program.

  4. Using with Polymorphic Objects: Enables managing polymorphic objects without worrying about deleting derived types correctly.

struct Base { virtual ~Base() = default; };
struct Derived : Base {};
std::unique_ptr<Base> ptr = std::make_unique<Derived>();

Methods and Operations

Constructor

  • Default: std::unique_ptr<T>()

  • Null pointer: std::unique_ptr<T>(nullptr)

  • Take ownership: std::unique_ptr<T>(T* ptr)

Modifiers

  • release(): Releases ownership of the object and returns the raw pointer.

  • reset(): Deletes the currently owned object and optionally takes ownership of another.

  • swap(): Exchanges ownership with another std::unique_ptr.

Accessors

  • get(): Returns the raw pointer without releasing ownership.

  • operator*: Dereferences the stored pointer.

  • operator->: Accesses members of the stored object.


Limitations

  1. Not Copyable: You cannot pass std::unique_ptr by value.

  2. Ownership Responsibility: Requires careful ownership management when transferring with std::move.


Comparison with Other Smart Pointers

  1. std::shared_ptr:

    • Allows shared ownership.

    • Uses reference counting, which introduces overhead.

  2. std::weak_ptr:

    • Non-owning reference to a std::shared_ptr object.

std::unique_ptr is more lightweight and ensures strict ownership compared to std::shared_ptr.


Code Example: Comprehensive Usage

#include <iostream>
#include <memory>

struct Resource {
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void sayHello() const { std::cout << "Hello from Resource\n"; }
};

void useResource(std::unique_ptr<Resource> res) {
    res->sayHello();
    // Resource will be destroyed when function exits.
}

int main() {
    auto res = std::make_unique<Resource>();
    useResource(std::move(res)); // Transfer ownership
    if (!res) {
        std::cout << "Resource ownership transferred\n";
    }

    // Array example
    auto arr = std::make_unique<int[]>(5);
    for (int i = 0; i < 5; ++i) arr[i] = i;

    for (int i = 0; i < 5; ++i) std::cout << arr[i] << " ";
    std::cout << std::endl;

    return 0;
}

Conclusion

std::unique_ptr is a powerful tool for managing dynamic memory in modern C++. Its single-ownership model ensures safe and efficient resource management, making it an essential component of modern C++ programming.

Last updated