Smart Pointers.
Example : Smart pointer ( unique pointer ) vis-à-vis a raw pointer.
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class AudioBook {
private:
string title;
string author;
public:
AudioBook() {}
AudioBook (string arg_title, string arg_author): title(arg_title), author(arg_author)
{}
inline void PlayBook() {
cout << "Playing : " << title << endl;
}
};
void With_UniquePointer(void) {
unique_ptr<AudioBook> uptr_a(new AudioBook("Lord Of The Rings", "J. R. R. Tolkien"));
uptr_a->PlayBook();
// Note : make_unique function efficiently creates and returns a unique pointer and is recommended over
// directly calling unique_ptr constructor.
auto uptr_b = make_unique<AudioBook>("Game Of Thrones", "George RR Martin");
uptr_b->PlayBook();
}
void With_RawPointer(void) {
AudioBook * rawptr = new AudioBook("The Hitchhiker's Guide to the Galaxy", "Douglas Adams");
rawptr->PlayBook();
// Make sure that raw pointer (rawptr) is deleted, else it would cause memory leak.
delete rawptr;
}
int main() {
With_UniquePointer();
With_RawPointer();
return 0;
}
Output
Playing : Lord Of The Rings
Playing : Game Of Thrones
Playing : The Hitchhiker's Guide to the Galaxy
Unique pointer ( std :: unique_ptr ) | Shared pointer ( std :: shared_ptr ) |
---|---|
- Unique pointer ( std :: unique_ptr ) does not share the ownership of the underlying raw pointer. - Unique pointer cannot be copied into some other unique pointer, also it cannot be passed by value to a function. A unique_pointer can only be moved. - make_unique function creates and returns a unique_pointer to an object constructed using the provided arguments. - make_unique provides exception safety and is preferred over calling unique_ptr constructors. - Object pointed by unique pointer is destroyed and its memory deallocated when either of the following happens: 1. The unique_ptr object is destroyed. 2. The unique_ptr object is assigned another pointer via operator= or reset(). |
- Shared pointer ( std :: shared_ptr ) shares the ownership of the underlying raw pointer while keeping a count of the references to the dynamically allocated object. - Shared pointer after initialization can be copied into another shared_pointer, and can be passed by value in function arguments. - make_shared function creates and returns a shared_pointer to an object constructed using the provided arguments. - make_shared provides exception safety and is preferred over calling shared_ptr constructors. - Object pointed by shared pointer is destroyed and its memory deallocated when either of the following happens: 1. The last shared_ptr managing the object is destroyed. 2. The last shared_ptr managing the object is assigned another pointer via operator= or reset(). |
Consider the below scenario where an object A ( Google stock object ) has a reference to object B ( Tesla stock object) and vice-versa.
#include <iostream>
#include <memory>
class Stock
{
private:
std :: string stock_name;
std :: shared_ptr<Stock> purchase;
public:
Stock (const std::string& name) : stock_name (name) {
std :: cout << "Stock object [" << stock_name << "] created" << std :: endl;
}
~Stock () {
std :: cout << "Stock object [" << stock_name << "] destroyed" << std :: endl;
}
// The Transact function purchases stock_b with stock_a and vice-versa thereby creating a circular references.
static void Transact (std :: shared_ptr<Stock>& stock_a, std :: shared_ptr<Stock>& stock_b) {
if (stock_a && stock_b) {
stock_a->purchase = stock_b;
stock_b->purchase = stock_a;
std :: cout << "Transaction 1" << std :: endl;
std :: cout << "Stock [" << stock_a->stock_name << "] purchased with stock [" << stock_b->stock_name << "]\n";
std :: cout << "Transaction 2" << std :: endl;
std :: cout << "Stock [" << stock_b->stock_name << "] purchased with stock [" << stock_a->stock_name << "]\n";
}
}
};
int main() {
auto tesla { std :: make_shared<Stock>("Tesla") };
auto google { std :: make_shared<Stock>("Google") };
// Purchase Tesla stock with Google and vice-versa
Stock :: Transact (tesla, google);
return 0;
}
Note : The destructor of the Stock class doesn’t get called even though the shared pointers ( tesla & google ) go out of scope and cause a memory leak.
We notice
2 shared pointers pointing to Tesla object. One being std :: shared_ptr ( tesla ) and the other is Google’s ( purchase )
2 shared pointers pointing to Google object. One being std :: shared_ptr ( google ) and the other is Tesla’s ( purchase )
At the end of the main function, before std :: shared_ptr ( tesla ) goes out of scope it checks if there are any references to Tesla’s object.
Since’s Google’s std :: shared_ptr ( purchase ) still holds a reference on Tesla’s object, tesla’s destructor doesn’t get called hence Tesla’s object remains in memory. The same thing happens to std :: shared_ptr ( google ). google’s destructor doesn’t get called because Tesla’s std :: shared_ptr ( purchase ) has a reference on Google’s object.
Output
Stock object [Tesla] created
Stock object [Google] created
Transaction 1
Stock [Tesla] purchased with stock [Google]
Transaction 2
Stock [Google] purchased with stock [Tesla]
The memory leak caused by a std :: shared_ptr in circular references can be fixed using a weak pointer ( std :: weak_ptr )
Note : std :: weak_ptr does not fix the circular references problem. A design change would be needed to address the circular reference problem (which is beyond the scope of this article).
Weak pointer ( std :: weak_ptr ) |
---|
- Weak pointer ( std :: weak_ptr ) holds a weak ( non - owning ) reference to the dynamically allocated object that is managed by a shared pointer ( std :: shared_ptr ). i.e The weak pointer acts as an observer and can access the object owned by a std :: shared_ptr; but does not own the object. - Weak pointer ( std :: weak_ptr ) cannot be used directly as it does not have -> operator. It has to be converted into a shared pointer ( std :: shared_ptr ) to access the dynamically allocated object. |
Addressing memory leak
When the std :: shared_ptr goes out of scope, it only considers the references held on the dynamically created object that it points to by other std :: shared_ptr and not by std :: weak_ptr. So if ( purchase ) is an std :: weak_ptr, the reference that it holds on the Stock object is not considered when the std :: shared_ptr goes out of scope.
Thus in the above example,
#include<iostream>
#include<memory>
#include<string>
class AudioBook {
private:
std::string title;
std::string author;
public:
AudioBook() {}
AudioBook (std::string arg_title, std::string arg_author): title(arg_title), author(arg_author) {
std :: cout << "Parameterized constructor for AudioBook got called." << std :: endl;
}
inline void PlayBook() {
std::cout << "Playing : " << title << std::endl;
}
~AudioBook() {
std :: cout << "Destructor for AudioBook got called." << std :: endl;
}
};
int main() {
auto uptr_a = std::make_unique<AudioBook>("Lord Of The Rings", "J. R. R. Tolkien");
uptr_a->PlayBook();
auto uptr_b = std::move(uptr_a);
uptr_b->PlayBook();
uptr_b.reset(); // Memory freed as nobody owns it now.
return 0;
}
Output
Parameterized constructor for AudioBook got called.
Playing : Lord Of The Rings
Playing : Lord Of The Rings
Destructor for AudioBook got called.
#include<iostream>
#include<memory>
#include<string>
class AudioBook {
private:
std :: string title;
std :: string author;
public:
AudioBook() {}
AudioBook (std :: string arg_title, std :: string arg_author) : title(arg_title), author(arg_author) {
std :: cout << "Parameterized constructor for AudioBook got called" << std :: endl;
}
inline void PlayBook() {
std::cout << "Playing : " << title << std :: endl;
}
~AudioBook() {
std :: cout << "Destructor for AudioBook got called" << std :: endl;
}
};
int main() {
auto shared_ptr_a = std :: make_shared<AudioBook>("Sherlock Holmes", "Arthur Conan Doyle");
shared_ptr_a->PlayBook();
{
// Initialization via assignment operator increments the reference count.
auto shared_ptr_b = shared_ptr_a;
shared_ptr_b->PlayBook();
// Initialization via copy constructor increments the reference count.
auto shared_ptr_c(shared_ptr_b);
shared_ptr_c->PlayBook();
// When the below shared pointer that is not being shared with others goes out of scope,
// the destructor gets called.
auto shared_ptr_scoped = std :: make_shared<AudioBook>("Rabbit & Bear", "Julian Gough");
shared_ptr_scoped->PlayBook();
}
// Shared pointer can also be initialized with null pointer
auto shared_ptr_d(nullptr);
std :: cout << "Program ends. Returning now" << std :: endl;
return 0;
}
Output
Parameterized constructor for AudioBook got called
Playing : Sherlock Holmes
Playing : Sherlock Holmes
Playing : Sherlock Holmes
Parameterized constructor for AudioBook got called
Playing : Rabbit & Bear
Destructor for AudioBook got called
Program ends. Returning now
Destructor for AudioBook got called
#include <iostream>
#include <memory>
class Stock
{
private:
std :: string stock_name;
// Shared pointer [ std :: shared_ptr<Stock> ] would give rise to circular references, causing a memory leak.
// The memory leak could be fixed by making purchase a weak pointer [ std :: weak_ptr<Stock> ]
std :: weak_ptr<Stock> purchase;
public:
Stock (const std :: string& name) : stock_name (name) {
std :: cout << "Stock object [" << stock_name << "] created" << std :: endl;
}
~Stock () {
std :: cout << "Stock object [" << stock_name << "] destroyed" << std :: endl;
}
// // The Transact function purchases stock_b with stock_a and vice-versa thereby creating a circular references.
static void Transact (std :: shared_ptr<Stock>& stock_a, std :: shared_ptr<Stock>& stock_b) {
if (stock_a && stock_b) {
stock_a->purchase = stock_b;
stock_b->purchase = stock_a;
std :: cout << "Transaction 1" << std :: endl;
std :: cout << "Stock [" << stock_a->stock_name << "] purchased with stock [" << stock_b->stock_name << "]\n";
std :: cout << "Transaction 2" << std :: endl;
std :: cout << "Stock [" << stock_b->stock_name << "] purchased with stock [" << stock_a->stock_name << "]\n";
}
}
};
int main() {
auto tesla { std :: make_shared<Stock>("Tesla") };
auto google { std :: make_shared<Stock>("Google") };
// Purchase Tesla stock with Google and vice-versa
Stock :: Transact (tesla, google);
return 0;
}
Output
Stock object [Tesla] created
Stock object [Google] created
Transaction 1
Stock [Tesla] purchased with stock [Google]
Transaction 2
Stock [Google] purchased with stock [Tesla]
Stock object [Google] destroyed
Stock object [Tesla] destroyed