We didn't have to call delete to deallocate memory and call the destructor.
Solving memory leaks and exceptions
Now take a look at the following code:
This is the desired result.
Here coded the same but with a regular pointer
We see here that we didn't calldelete ptr which causes a memory leak.
Now obviously we could have done some like this which would have solved our problems but still this may not be possible in every case or maybe more cumbersome
std::unique_ptr<T>
unique_ptr follows the exclusive ownership semantics, i.e., at any point of time, the resource is owned by only one unique_ptr.
When unique_ptr goes out of scope, the resource is released.
If the resource is overwritten by some other resource, the previously owned resource is released. So it guarantees that the associated resource is released always.
Example
std::move(uniqPtr) allows us to transfer ownership.
as stated before the following is not allowed
This is becaues unique_ptr copy ctor and copy assignment look like this
A sub-category is make_unique
Here we didn't use new as before like in uniqPtr(new SomeClass(100)) just the class name.
Multiple shared pointers can refer to a single object
When the last shared pointer goes out of scope, memory is released automatically. This is known by reference counting.
Creation
if created with the syntax above the shared_ptrreleases the associated resource by calling delete by default
Custom deletion
If the user needs a different destruction policy, he/she is free to specify the same while constructing the shared_ptr.
Here we game a lambda expression to call delete[] instead
Member functions
get() : To get the resource associated with the shared_ptr.
reset(): To yield the ownership of the associated memory block. If this is the last shared_ptr owning the resource, then the resource is released automatically.
unique(): (until C++20) To know whether the resource is managed by only this shared_ptr instance.
use_count(): get number of shared resources
operator bool: To check whether the shared_ptr owns a memory block or not. Can be used with an if condition. if (ptr) {
Example 1
Here we see that the destructor is only called when the number of references goes down to 0. Also we notice that on line 22 which is } the ref count goes down 1.
Example 2
Notice how Calling destructor1 was called before. This is because of reset()
Note: if we were to do the following std::make_shared<SomeClass>( SomeClass(2)) we would see the dtor twice. See here for more info
The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.
std::shared_ptr manages two entities:
the control block (stores meta data such as ref-counts, type-erased deleter, etc)
the object being managed
std::make_shared performs a single heap-allocation accounting for the space necessary for both the control block and the data. Here also we don't use the new keyword just as make_unique
#include <iostream>
#include <memory>
using std::unique_ptr, std::cout, std::endl;
struct SomeClass{
static int cntr;
int m_a;
SomeClass() { m_a=cntr++; }
SomeClass(int a) : m_a(a) { cntr++; }
~SomeClass() { cout << "Calling destructor" << m_a <<endl; }
};
int SomeClass::cntr = 0;
int main(){
unique_ptr<SomeClass> uniqPtr(new SomeClass());
//std::unique_ptr<SomeClass> uniqPtr1 (uniqPtr); // NOT POSSIBLE
unique_ptr<SomeClass> uniqPtr1 (std::move(uniqPtr));
if (!uniqPtr)
cout << "Uniqptr is empty now" << endl;
return 0;
}
Uniqptr is empty now
Calling destructor0
unique_ptr<SomeClass> uniqPtr(new SomeClass(100));
unique_ptr<SomeClass> uniqPtr1 (uniqPtr); // NOT POSSIBLE
//or this
unique_ptr<SomeClass> uniqPtr2 = uniqPtr; // NOT POSSIBLE