unique_ptr

Why RAII? 

C++ library to manage their own resources follow RAII since C++20

Smart Pointers

The entire job of a smart pointer is to manage dynamically allocated memory. 

If your function takes unique ownership:

  • Pass unique_ptr by value (ownership is moved, source becomes nullptr) 

Refer program 8 in this post.

If your function is a new shared owner

  • Pass shared_ptr by value (ownership is copied)

If your function uses the object without taking ownership and with its existence guaranteed

  • Pass by (const) reference

Program 1 (Before Unique_ptr)

#include <iostream>
#include <memory>

int functionFoo (int var) 
{
    int *ptr = new int;
    std::cout << "Variable is " << var << " ptr is " << ptr << std::endl;
    
    *ptr = var;
    int res = *ptr;
    res += 1;

    delete ptr;
    std::cout << "Vec is " << ptr << " res " << res << std::endl;
    return res;
}

int main()
{
    int ret = functionFoo(23);
    std::cout << "return " << ret << std::endl;

    return 0;
}

Output:

1
2
3
Variable is 23 ptr is 0x934c438
Vec is 0x934c438 res 24
return 24

Click here to debug the above program. 

Let's try to convert the program 1 to have the unique_ptr instead of raw pointers. 

Printing unique_ptr using cout

Program 2 (Conversion of raw pointer to Unique_ptr)

make_unique is supported from C++14 onwards.

#include <iostream>
#include <memory>

int functionFoo (int var) 
{
    // Method 1: assign value while allocation
    std::unique_ptr<int> uptr = std::make_unique<int>(var);
    std::cout << "Variable is " << var << " uptr addr " 
              << uptr.get() << " value " << *uptr << std::endl;
    
    // Method 2: assigning value to unique_ptr's ptr
    std::unique_ptr<int> uptr2 = std::make_unique<int>();
    *uptr2 = var;
    std::cout << "Variable is " << var << " uptr2 addr " 
              << uptr2.get() << " value " << *uptr2 <<  std::endl;
    *uptr2 += 20;
    std::cout << "Value changed \n"  << "uptr2 addr " 
              << uptr2.get() << " value " << *uptr2 <<  std::endl;
              
    // Method 3: using move
    std::unique_ptr<int> uptr3 = std::move(uptr);
    std::cout << "Variable is " << var << " uptr3 addr " 
              << uptr3.get() << " value " << *uptr3 <<  std::endl;
    std::cout << "\nAccessing uptr \n" << "uptr addr " 
              << uptr.get() << std::endl;
     
    // Method 4: allocate unique_ptr and assign to auto
    auto uptr4 = std::make_unique<int>(var);
    std::cout << "Variable is " << var << " uptr4 addr " 
              << *uptr4 << std::endl;        
              
    int res = *uptr2;
    res += 1;
    std::cout << " res " << res << std::endl;
    return res;
}

int main()
{
    int ret = functionFoo(23);
    std::cout << "return " << ret << std::endl;

    return 0;
}

Output:

1
2
3
Variable is 23 uptr addr 0x56108a708eb0 value 23
Variable is 23 uptr2 addr 0x56108a7092e0 value 23
Value changed 
uptr2 addr 0x56108a7092e0 value 43
Variable is 23 uptr3 addr 0x56108a708eb0 value 23

Accessing uptr 
uptr addr 0
Variable is 23 uptr4 addr 23
 res 44
return 44

Click here to debug the above program.

Vector of Unique_ptr

Program 3 (Assigning local variable to unique_ptr)

#include <memory>
#include <vector>
#include <iostream>

int main()
{
    std::vector<std::unique_ptr<int>> vec;

    for (int i = 0; i < 5; i++) {
        int x(i);
        std::unique_ptr<int> ptr2x(&x);
        vec.push_back(std::move(ptr2x)); 
    }
    
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    vec.erase(vec.begin() + 2);

    std::cout << vec.size() << std::endl;
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    
    
    return 0;
}

Output:

1
2
3
0x7fff9abb15a0 4
0x7fff9abb15a0 4
0x7fff9abb15a0 4
0x7fff9abb15a0 4
0x7fff9abb15a0 4
double free or corruption (out)

A new local integer x is created. Then we are assigning the address of the local variable to std::unique_ptr as if it were allocated by new. This is why the address is always the same. In every loop iteration a new x is created on the stack at the same location.

At the end of the loop body x goes out of scope, so the unique_ptr now points at a random memory location. So we cant even get the values anymore, because the particular x we are pointing at no longer exists.

After that once you try to erase from the vector or try to destroy it, the unique_ptr  will try to call delete on the address of that local address that no longer exists and also wasn't allocated with  new.

So, the program crashed at its best. It blew it spectacularly. 

Note: unique_ptr always go with make_unique

Program 4 A

#include <memory>
#include <vector>
#include <iostream>

int main()
{
    std::vector<std::unique_ptr<int>> vec;

    for (int i = 0; i < 5; i++) {
        auto ptr2x = std::make_unique<int>(i);
        vec.push_back(std::move(ptr2x)); 
    }
    
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    vec.erase(vec.begin() + 2);

    std::cout << vec.size() << std::endl;
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    
    return 0;
}

Output:

1
2
3
0x562ddb318eb0 0
0x562ddb318ef0 1
0x562ddb318ed0 2
0x562ddb318f10 3
0x562ddb318f60 4
4
0x562ddb318eb0 0
0x562ddb318ef0 1
0x562ddb318f10 3
0x562ddb318f60 4

Another version of 4A

#include <memory>
#include <vector>
#include <iostream>

int main()
{
    std::vector<std::unique_ptr<int>> vec;

    for (int i = 0; i < 5; i++) {
        auto ptr2x = std::make_unique<int>(i);
        vec.push_back(std::move(ptr2x)); 
    }
    for (auto i = vec.begin(); 
            i != vec.end(); i++) { 
	     std::cout << (*i).get() << " " << *(*i).get() << std::endl;
	}
	
    vec.erase(vec.begin() + 2);

    std::cout << vec.size() << std::endl;
    for (auto i = vec.begin(); 
            i != vec.end(); i++) { 
         std::cout << (*i).get() << " " << *(*i).get() << std::endl;
    }
    
    return 0;
}

Output:

1
2
3
0x55878a951eb0 0
0x55878a951ef0 1
0x55878a951ed0 2
0x55878a951f10 3
0x55878a951f60 4
4
0x55878a951eb0 0
0x55878a951ef0 1
0x55878a951f10 3
0x55878a951f60 4

Program 4 B

#include <memory>
#include <vector>
#include <iostream>

int main()
{
    std::vector<std::unique_ptr<int>> vec;

    for (int i = 0; i < 5; i++) {
        vec.push_back(std::make_unique<int>(i)); 
    }
    
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    vec.erase(vec.begin() + 2);

    std::cout << vec.size() << std::endl;
    for (int i = 0; i != vec.size(); i++) {
         std::cout << vec.at(i).get() << " " << *vec.at(i).get() << std::endl;
    }
    
    return 0;
}

Output:

1
2
3
0x56452604aeb0 0
0x56452604aef0 1
0x56452604aed0 2
0x56452604af10 3
0x56452604af60 4
4
0x56452604aeb0 0
0x56452604aef0 1
0x56452604af10 3
0x56452604af60 4

You can find the link for this program here & GDBOnlineDebugger

Program 5 (Assigning ptr to unique_ptr)

#include <iostream>
#include <memory>

int functionFoo (int var) 
{
    // Method 1: assign value while allocation
    int *ptr = new int(10);
    std::cout << "ptr is " << ptr << "   &ptr is " 
              << &ptr << " *ptr " << *ptr << std::endl;
    std::unique_ptr<int> uptr(ptr);
    std::cout << "Uptr.get() " << uptr.get()  << " &uptr" 
              << &uptr << " *uptr " << *uptr << std::endl;
              
    int res = *uptr;
    res += 1;
    std::cout << " res " << res << std::endl;
    return res;
}

int main()
{
    int ret = functionFoo(23);
    std::cout << "functionFoo returns " << ret << std::endl;

    return 0;
}

Output:

1
2
3
ptr is 0x55cb956f4eb0   ptr addr 0x7ffe74614798 value 10
Uptr is 0x55cb956f4eb0 uptr addr 0x7ffe746147a0 value 10
 res 11
functionFoo returns 11


Note: This tells us that the value stored in unique_ptr can be got by uptr.get() 

Debug here the above program.

Returning unique_ptr & check against nullptr

Program 6

#include <iostream>
#include <memory>

std::unique_ptr<int> functionFoo (int var) 
{
    std::unique_ptr<int> uptr = std::make_unique<int>(var);
    std::cout << "Variable is " << var << " uptr addr " 
              << uptr.get() << " value " << *uptr << std::endl;
    if (uptr == nullptr) {
        return nullptr;
    }
    *uptr += 1;
    return uptr;
}

int main()
{
    auto ret = functionFoo(23);
    if (ret.get() == nullptr) {
        return 0;  
    }
    std::cout << *ret << std::endl;
    return 1;
}

Output:

1
2

Variable is 23 uptr addr 0x56512f42feb0 value 23
24

We can use uptr or uptr.get to check against nullptr. 

Returning unique_ptr - Variations (Must Read *)

Program 7

#include <iostream>
#include <memory>

class Base {
    public:
    virtual void hello() {
        std::cout << "hello" << std::endl;
    } 
};

class Derived : public Base {
    public:
    void hello() override {
        std::cout << "derived hello" << std::endl;
    } 
};

// return make_unique 
std::unique_ptr<Base> GetDerived()
{
    std::cout << "GetDerived()" << std::endl;
    return std::unique_ptr<Base>(std::make_unique<Derived>());
    // The below line equals to the above.
    //return std::make_unique<Derived>(); 
}

// return auto ptr instead of unique_ptr  
std::unique_ptr<Base> GetDerived1()
{
    std::cout << "GetDerived1() - Method 1" << std::endl; 
    auto a = std::make_unique<Derived>(); 
    return a;
}

#if 0
// return by casting lvalue  
std::unique_ptr<Base> GetDerived2()
{
    std::cout << "GetDerived2() - Method 2" << std::endl; 
    std::unique_ptr<Derived> a = std::make_unique<Derived>(); 
    return std::unique_ptr<Base>(a);
}
#endif

// return as move(lvalue) 
std::unique_ptr<Base> GetDerived3()
{
    std::cout << "GetDerived3()" << std::endl; 
    auto a = std::make_unique<Derived>(); 
    return std::move(a);
}

// return auto ptr as lvalue  
std::unique_ptr<Base> GetDerived4()
{
    std::cout << "GetDerived4()" << std::endl; 
    auto a = std::make_unique<Base>(); 
    return a;
}

// return auto ptr lvalue by casting 
std::unique_ptr<Base> GetDerived5()
{
    std::cout << "GetDerived5()" << std::endl; 
    auto a = std::make_unique<Base>(); 
    return std::move(a);
}

int main()
{
    std::unique_ptr<Base> gd = GetDerived();
    std::unique_ptr<Base> gd1 = GetDerived1();
    std::unique_ptr<Base> gd3 = GetDerived3();
    std::unique_ptr<Base> gd4 = GetDerived4();
    std::unique_ptr<Base> gd5 = GetDerived5();
    return 1;
}

Output:

1
2

GetDerived()
GetDerived1() - Method 1
GetDerived3()
GetDerived4
GetDerived5()


In GetDerived,   std::make_unique<Derived>() is rvalue, so it is move constructed.

In GetDerived1,  passing the auto pointer to the function is a poor choice as std::make_unique<Base> is not even constructed. Accessing the elements of the Base in the function might not work. 

GetDerived2 will throw the below error because a is lvalue which is copy constructed. 
Though a is lvalue, it is unique_ptr which is move constructed. Hence, this will not work. 
error: no matching function for 
call to std::unique_ptr::unique_ptr(std::unique_ptr >&)

In GetDerived3, the lvalue a is casted to a rvalue using move semantics. 

In GetDerived4, auto ptr can be passed to a function as make_unique of Base is already constructed unlike GetDerived1. 


Click here to debug further.

Move semantics

Unique_ptr has no copy constructor but has only move constructor.

unique_ptr( unique_ptr&& u );         // move ctor

  • std::move should be used only to move the lvalue because the rvalue is already move constructed.
Note:  Simple trick to remember is the letter "L" is adjacent to "M" in alphabets, so only lvalue requires move. 

Passing Unique_ptr to a function (Must Read *)

Generally, unique_ptr can't be passed to a function because it doesn't have copy constructor.  If it still needs to be passed to a function, it should be moved using std::move. 

If it needs to be passed as a reference, it can passed as such. 

Program 8 (Must Read *)

#include <iostream>
#include <memory>

class Base {
    private:
       int _v1;
       
    public:
    Base(int v) : _v1(v) 
    { std::cout << "Base::Ctor " << std::endl;}
    virtual int GetVal() {
        std::cout << "Base::GetVal() \t";
        return _v1;
    } 
};

class Derived : public Base {
    private:
       int _v1;
       
    public:
    Derived(int v) : Base(v), _v1(v) 
    { std::cout << "Derived::Ctor " << std::endl; }
    int GetVal() override{
        std::cout << "Derived::GetVal() \t";
        return _v1;
    } 
};

// Pass by Value
int GetDerived3(std::unique_ptr<Base> ptr)
{
    std::cout << "GetDerived3()\t" 
              << "ptr.get() " << ptr.get() 
              << " " << ptr->GetVal() << std::endl; 
    return 1;
}

// Pass by reference
int GetDerived4(std::unique_ptr<Base> & ptr)
{
    std::cout << "GetDerived4()\t" 
              << ptr->GetVal() << std::endl; 
    return 1;
}


int main()
{
    auto a = std::make_unique<Derived>(23); 
   std::cout << "main() a \t" 
              << "a.get() " << a.get() << std::endl;
    GetDerived3(std::move(a)); 
    //GetDerived4(a);
    std::cout << "After move in main() a \t" 
              << "a.get() " << a.get() << "\n\n"; 
    std::unique_ptr<Base> b = std::make_unique<Derived>(24);
    GetDerived4(b);
    std::cout << "main() b \t" 
              << "b.get() " << b.get()
              << " " << b->GetVal() << std::endl; 
    
    return 1;
}

Output:

1
2
Base::Ctor 
Derived::Ctor 
main() a        a.get() 0x5605b3bb3eb0
GetDerived3()   ptr.get() 0x5605b3bb3eb0 Derived::GetVal()      23
After move in main() a  a.get() 0

Base::Ctor 
Derived::Ctor 
main() b        b.get() 0x5605b3bb3eb0 Derived::GetVal()        24
GetDerived4()   Derived::GetVal()       24
After passing to GetDerived4    b.get() 0x5605b3bb3eb0 Derived::GetVal()        24


We should be careful to maintain the ownership of the source, so always try to use 'const &'. 

Note:  You can never return an auto pointer unless it is constructed. (Must Read *)
Click here to debug.

Assigning Unique_ptr to a raw pointer (Must Read *)

If you want to get rid of smart pointer, but still want to hold the pointer, you can assign the unique_ptr to raw pointer.

Program 9 (Must Read *)

#include <iostream>
#include <memory>

class Car {
public:
    Car (std::string color) : color_(color) {}

private:
    virtual void brand() {
        /* Some functionality */ 
    }
protected:
        std::string color_;
friend class Driver;
};

class Tesla : public Car {
public:
    Tesla (std::string color) : Car(color) {}
    void brand() override {
        /* Some functionality */
    }
};

class Benz : public Car {
public:
    Benz (std::string color) : Car(color) {}
    void brand() override {
        std::cout << "Hello Benz" << std::endl;
    }
};

class Driver {
public:
    void brand() {
        Car *c1 = obj.release(); 
        c1->brand();
    }
    void addCar(std::unique_ptr<Car> c) {
        obj = std::move(c);
    }
    Driver() {}
private:
    std::unique_ptr<Car> obj;
};


int main()
{
    Driver d1;
    std::unique_ptr<Benz> b1 = std::make_unique<Benz>("black");
    d1.addCar(std::move(b1));
    d1.brand();

    return 0;
}

Output:

Hello Benz

Unfortunately this code leaks a memory in the below line.

Car *c1 = obj.release();

It release the object from unique_ptr but not destroyed it. We just dropped the pointer. So, it will be resolved by the below program. 

Program 10 (Must Read *)

#include <iostream>
#include <memory>

class Car {
public:
    Car (std::string color) : color_(color) {}

private:
    virtual void brand() {
        /* Some functionality */ 
    }
protected:
        std::string color_;
friend class Driver;
};

class Tesla : public Car {
public:
    Tesla (std::string color) : Car(color) {}
    void brand() override {
        /* Some functionality */
    }
};

class Benz : public Car {
public:
    Benz (std::string color) : Car(color) {}
    void brand() override {
        std::cout << "Hello Benz" << std::endl;
    }
};

class Driver {
public:
    void brand() {
        Car *c1 = obj.get(); 
        c1->brand();
    }
    void addCar(std::unique_ptr<Car> c) {
        obj = std::move(c);
    }
    Driver() {}
private:
    std::unique_ptr<Car> obj;
};


int main()
{
    Driver d1;
    std::unique_ptr<Benz> b1 = std::make_unique<Benz>("black");
    d1.addCar(std::move(b1));
    d1.brand();

    return 0;
}

Output:

Hello Benz

Advantages 

1) make_unique provides exception safety.  

For example, when you make a call like 
func(new A(), new B());                   

The compiler may choose to evaluate the function arguments from left to right, or in any order it so wishes. 

Let's assume left to right evaluation: What happens when the first new expression succeeds but the second new expression throws?

The real danger here is when you catch such exception; Yes, you may have caught the exception thrown by new B(), and resume normal execution, but new A() already succeeded, and its memory will be silently leaked. Nobody to clean it up... * sobs...

But with make_unique, you cannot have a leak because, stack unwinding will happen ( and the destructor of the previously created object will run). Hence, having a preference for make_unique will constrain you towards exception safety.

2) To pass by value without copying

See Program 8 above. 

Reference

nullptr check in unique_ptr 



https://docs.microsoft.com/en-us/cpp/cpp/how-to-create-and-use-unique-ptr-instances?view=msvc-170


https://en.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr


https://medium.com/swlh/c-smart-pointers-and-how-to-write-your-own-c0adcbdce04f#:~:text=unique_ptr%20allows%20only%20one%20owner,a%20reference%2Dcounted%20smart%20pointer.&text=In%20this%20implementation%2C%20the%20developer,the%20end%20of%20the%20function.

https://stackoverflow.com/questions/48398060/error-while-passing-stdunique-ptr-to-class

https://eli.thegreenplace.net/2012/06/20/c11-using-unique_ptr-with-standard-library-containers

https://www.learncpp.com/cpp-tutorial/stdunique_ptr/

https://coliru.stacked-crooked.com/a/2cc4242ebd2cec9b - https://www.reddit.com/r/cpp_questions/comments/vrv0sx/code_review_of_unique_ptr_implementation/

Comments