In C++, smart pointers are wrapper classes that manage dynamically allocated memory (memory on the heap created with new). They automate the process of memory deallocation (calling delete or delete[]) by leveraging a C++ feature called RAII (Resource Acquisition Is Initialization). This means when the smart pointer object itself goes out of scope, its destructor is called, which in turn releases the managed memory. This helps prevent common C++ pitfalls like:

  1. Memory Leaks: Forgetting to delete dynamically allocated memory.
  2. Dangling Pointers: Accessing memory that has already been deallocated.
  3. Double Deletion: Trying to delete the same memory twice.

Modern C++ (C++11 and later) provides three main types of smart pointers in the <memory> header:

  1. std::unique_ptr: Exclusive ownership.

    std::unique_ptr

    decltype in c++

  2. std::shared_ptr: Shared ownership (reference counted).

    std::shared_ptr

  3. std::weak_ptr: Non-owning observer of a std::shared_ptr.

    std::weak_ptr

    understand lock in weak pointers

Here is a concise summary of all 3 types of smart pointers with their member functions in use:

member functions in smart pointers

Let's dive into each one.


1. std::unique_ptr

Syntaxes:

#include <iostream>#include <memory> // Required for smart pointers#include <string>#include <vector>// A sample class
classResource {
public:
    Resource(int id) : id_(id) { std::cout << "Resource " << id_ << " acquired.\\n"; }
    ~Resource() { std::cout << "Resource " << id_ << " released.\\n"; }
    voidshow() const { std::cout << "Resource ID: " << id_ << "\\n"; }
private:
    int id_;
};

// --- unique_ptr Syntaxes ---

// 1. Creation (preferred method using std::make_unique, C++14+)
// std::unique_ptr<Type> ptr_name = std::make_unique<Type>(constructor_args);
std::unique_ptr<Resource> u_ptr1 = std::make_unique<Resource>(1);

// 2. Creation (using new, C++11 - less safe due to potential exceptions between new and unique_ptr construction)
// std::unique_ptr<Type> ptr_name(new Type(constructor_args));
std::unique_ptr<Resource>u_ptr2(new Resource(2));

// 3. Accessing the managed object
// ptr_name->member_function();
// (*ptr_name).member_variable; // Less common
if (u_ptr1) { // Check if it owns an object
    u_ptr1->show();
}

// 4. Getting the raw pointer (use with caution, doesn't transfer ownership)
// Type* raw_ptr = ptr_name.get();
Resource* raw_res_ptr = u_ptr1.get();
if (raw_res_ptr) {
    // raw_res_ptr->show(); // Be careful, lifetime still managed by u_ptr1
}

// 5. Releasing ownership (returns raw pointer, caller is now responsible for deletion)
// Type* raw_ptr = ptr_name.release();
Resource* raw_res_ptr_released = u_ptr2.release(); // u_ptr2 is now nullptr
// Now you MUST delete raw_res_ptr_released manually
// delete raw_res_ptr_released; // Done in the example

// 6. Resetting (deletes current object, optionally takes ownership of a new one)
// ptr_name.reset(); // Deletes managed object, ptr_name becomes nullptr
// ptr_name.reset(new Type(constructor_args)); // Deletes old, owns new
std::unique_ptr<Resource> u_ptr3 = std::make_unique<Resource>(3);
u_ptr3.reset(); // Resource 3 released here
u_ptr3.reset(new Resource(4)); // Resource 4 acquired

// 7. Moving ownership
std::unique_ptr<Resource> u_ptr5 = std::make_unique<Resource>(5);
std::unique_ptr<Resource> u_ptr6 = std::move(u_ptr5); // u_ptr5 is now nullptr
// u_ptr5->show(); // This would crash or be undefined behavior
if (u_ptr6) {
    u_ptr6->show();
}

// 8. For arrays (specialization)
// std::unique_ptr<Type[]> array_ptr_name = std::make_unique<Type[]>(size);
// std::unique_ptr<Type[]> array_ptr_name(new Type[size]);
std::unique_ptr<int[]> arr_ptr = std::make_unique<int[]>(5);
for (int i = 0; i < 5; ++i) {
    arr_ptr[i] = i * 10; // Access like an array
}
// arr_ptr goes out of scope, delete[] is called automatically

// 9. Custom Deleters
// Useful for resources not managed by new/delete (e.g., C file handles, custom allocators)
structFileCloser {
    voidoperator()(FILE* fp) const {
if (fp) {
            std::cout << "Closing file via custom deleter.\\n";
            fclose(fp);
        }
    }
};
// FILE* f = fopen("test.txt", "w");
// if (f) {
//     std::unique_ptr<FILE, FileCloser> file_ptr(f, FileCloser());
//     // Use file_ptr
//     fprintf(file_ptr.get(), "Hello from unique_ptr with custom deleter!\\n");
// } // File automatically closed when file_ptr goes out of scope