Detailed explanation of smart pointer

Keywords: C++ Visual Studio

unique_ptr is one of the smart pointers provided by C++ 11 to prevent memory leakage. It is a smart pointer that exclusively enjoys the ownership of the managed object pointer. unique_ The PTR object wraps a raw pointer and is responsible for its life cycle. When the object is destroyed, the associated original pointer is deleted in its destructor.
unique_ptr has - > and * operator overloads, so it can be used like a normal pointer.

#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout << "Task::Constructor" << std::endl;
    }
    ~Task() {
        std::cout << "Task::Destructor" << std::endl;
    }
};

int main()
{
    // Create unique from original pointer_ PTR instance
    std::unique_ptr<Task> taskPtr(new Task(23));

    //Through unique_ptr access its members
    int id = taskPtr->mId;
    std::cout << id << std::endl;

    return 0;
}
Output:
Task::Constructor
23
Task::Destructor

unique_ The PTR < task > object taskPtr accepts the original pointer as a parameter. Now, when the main function exits, the object will call its destructor when it is out of scope. In unique_ In the destructor of taskPtr of PTR object, the associated original pointer will be deleted, so there is no need to specifically delete the task object.
In this way, whether the function exits normally or abnormally (due to some exceptions), the destructor of taskPtr will always be called. Therefore, the original pointer will always be deleted and memory leakage will be prevented.

unique_ptr exclusive ownership

unique_ The PTR object is always the sole owner of the associated original pointer. We can't copy unique_ptr object, which can only move.
Because each unique_ptr objects are the only owner of the original pointer, so it directly deletes the associated pointer in its destructor without any reference count.

Create an empty unique_ptr object

Create an empty unique_ The PTR < int > object is empty because it has no original pointer associated with it.

Check unique_ Is the PTR object empty

There are two ways to check unique_ Whether the PTR object is null or has an original pointer associated with it.

// Method 1
if(!ptr1)
	std::cout<<"ptr1 is empty"<<std::endl;
// Method 2
if(ptr1 == nullptr)
	std::cout<<"ptr1 is empty"<<std::endl;

Create unique using the original pointer_ PTR object

To create a non empty unique_ptr object, you need to pass the original pointer in its constructor when creating the object, that is:

std::unique_ptr taskPtr(new Task(22));

Using std::make_unique create unique_ptr object / C++14

std::unique_ptr taskPtr = std::make_unique(34);

Gets the pointer to the managed object

Use the get() · function to get the pointer of the management object

Task *p1 = taskPtr.get();

Reset unique_ptr object

In unique_ Calling the reset() function on the PTR object will reset it, that is, it will release the original pointer associated with delete and make unique_ptr object is empty

taskPtr.reset();

unique_ptr object is not replicable

Because unique_ptr cannot be copied and can only be moved. Therefore, we cannot create unique by copying constructors or assignment operators_ A copy of the PTR object.

// Compilation error: unique_ptr cannot be copied
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error

// Compilation error: unique_ptr cannot be copied
taskPtr = taskPtr2; //compile error

Transfer unique_ Ownership of PTR objects

We can't copy unique_ptr objects, but we can transfer them. This means unique_ The PTR object can transfer ownership of the associated original pointer to another unique object_ PTR object. Let's understand through an example

// Create taskPtr2 from the original pointer
std::unique_ptr<Task> taskPtr2(new Task(55));
// Transfer ownership of the associated pointer in taskPtr2 to taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// The pointer associated with taskPtr2 is now null
if(taskPtr2 == nullptr)
	std::cout<<"taskPtr2 is  empty"<<std::endl;

// Ownership of the taskPtr2 Association pointer is now transferred to taskPtr4
if(taskPtr4 != nullptr)
	std::cout<<"taskPtr4 is not empty"<<std::endl;

// Will output 55
std::cout<< taskPtr4->mId << std::endl;

std::move() will convert taskPtr2 into an R-value reference. Therefore, unique is called_ PTR and transfer the associated original pointer to taskPtr4. After transferring ownership of the original pointer, taskPtr2 will become null.

Release the associated original pointer

In unique_ Calling release() on the PTR object releases ownership of its associated original pointer and returns the original pointer. Here is the release of ownership. There is no delete original pointer. reset() will delete the original pointer.

std::unique_ptr<Task> taskPtr5(new Task(55));
// Not empty
if(taskPtr5 != nullptr)
	std::cout<<"taskPtr5 is not empty"<<std::endl;
// Release ownership of associated pointers
Task * ptr = taskPtr5.release();
// Empty now
if(taskPtr5 == nullptr)
	std::cout<<"taskPtr5 is empty"<<std::endl;

Sample program

#include <iostream>
#include <memory>

struct Task {
    int mId;
    Task(int id ) :mId(id) {
        std::cout<<"Task::Constructor"<<std::endl;
    }
    ~Task() {
        std::cout<<"Task::Destructor"<<std::endl;
    }
};

int main()
{
    // Empty object unique_ptr
    std::unique_ptr<int> ptr1;

    // Check whether ptr1 is empty
    if(!ptr1)
        std::cout<<"ptr1 is empty"<<std::endl;

    // Check whether ptr1 is empty
    if(ptr1 == nullptr)
        std::cout<<"ptr1 is empty"<<std::endl;

    // Unique cannot be initialized by assignment_ ptr
    // std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error

    // Create unique from original pointer_ ptr
    std::unique_ptr<Task> taskPtr(new Task(23));

    // Check whether taskPtr is empty
    if(taskPtr != nullptr)
        std::cout<<"taskPtr is  not empty"<<std::endl;

    // Access unique_ Member of PTR Association pointer
    std::cout<<taskPtr->mId<<std::endl;

    std::cout<<"Reset the taskPtr"<<std::endl;
    // Reset unique_ptr is null, the associated original pointer will be deleted
    taskPtr.reset();

    // Check for null / check for associated raw pointers
    if(taskPtr == nullptr)
        std::cout<<"taskPtr is  empty"<<std::endl;

    // Create unique from original pointer_ ptr
    std::unique_ptr<Task> taskPtr2(new Task(55));

    if(taskPtr2 != nullptr)
        std::cout<<"taskPtr2 is  not empty"<<std::endl;

    // unique_ptr objects cannot be copied
    //taskPtr = taskPtr2; //compile error
    //std::unique_ptr<Task> taskPtr3 = taskPtr2;

    {
        // Transfer ownership (transfer pointer in unique_ptr to another unique_ptr)
        std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
        // Empty after transfer
        if(taskPtr2 == nullptr)
            std::cout << "taskPtr2 is  empty" << std::endl;
        // Not empty after transfer in
        if(taskPtr4 != nullptr)
            std::cout<<"taskPtr4 is not empty"<<std::endl;

        std::cout << taskPtr4->mId << std::endl;

        //taskPtr4 goes beyond the following parenthesis to delete the pointer associated with it
    }

    std::unique_ptr<Task> taskPtr5(new Task(66));

    if(taskPtr5 != nullptr)
        std::cout << "taskPtr5 is not empty" << std::endl;

    // Release ownership
    Task * ptr = taskPtr5.release();

    if(taskPtr5 == nullptr)
        std::cout << "taskPtr5 is empty" << std::endl;

    std::cout << ptr->mId << std::endl;

    delete ptr;

    return 0;
}
output:
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is  not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is  empty
Task::Constructor
taskPtr2 is  not empty
taskPtr2 is  empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor

summary

The new object is located on the heap memory. You must call delete to free its memory.
unique_ptr is a container containing pointers and has the unique ownership of associated pointers. When used as a common variable, the system allocates objects to the stack memory. When it exceeds the scope, it will be automatically destructed, unique_ The destructor of PTR object will delete its associated pointer, which is equivalent to deleting the object in heap memory for us.
unique_ptr cannot be copied directly. You must use std::move() to transfer its managed pointer. After the transfer, the original unique_ptr is empty. std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
How to create an object:

//C++11: 
std::unique_ptr<Task> taskPtr(new Task(23));
//C++14: 
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);

Posted by mkohan on Mon, 06 Dec 2021 15:41:32 -0800