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)
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; }
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)
#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; }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
error: no matching function for
call to ‘std::unique_ptr::unique_ptr(std::unique_ptr >&)’
Move semantics
unique_ptr( unique_ptr&& u ); // move ctor
- std::move should be used only to move the lvalue because the rvalue is already move constructed.
Passing Unique_ptr to a function (Must Read *)
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; }
|
Assigning Unique_ptr to a raw pointer (Must Read *)
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; }
Car *c1 = obj.release();
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; }
Advantages
func(new A(), new B())
; 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.
Reference
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
Post a Comment