Smart Pointer in C++
0. Overview
We have touched the pointer in C++ previously in Linked List part. This time, we’re going to handle the smart pointer in C++. Before dig into the detail, let’s have a comprehensive understanding of that.
We need to admit it that pointer can be a tricky to newbie, especially the de-allocation of pointer. When we forget the delete the pointer we created after using, it could lead to memory leak. Thus, smart pointer is here.
First of all, let’s recall that when we “delete ptr”, we delete the object pointed by ptr, instead the pointer itself. Thus,the principle of the smart pointer is to automatically delete the object that have no pointer point to it. To be more clear, for an object, if there is pointer pointed to it, then this object will be kept because some pointer need it; When there is no pointer point to it, it means this object is useless, which need to be delete to free the memory.
There are three types of smart pointer: unique_ptr, shared_ptr, weak_ptr.
As mentioned before, when there exists pointer point to the object, the object will be kept. But how to check this? We count the number of pointers that point to an object. This makes the difference among three types of smart pointers.
For unique_ptr, this object can only be pointed by this unique_ptr, and the count will and only will be one.
For shared_ptr, it’s the most frequent used type. The object’s count is the number of sharted_ptr pointed to it.
For weak_ptr, as its name, it cannot count as a pointer, but it exists. We could take this as a direction, we could use weak_ptr to track something, but we cannot count on weak_ptr to help us keep the object; Only unique_ptr and shared_ptr could. However, when we want weak_ptr to get the information of the object, we could use function weak_ptr->lock() to cast type from weak_ptr to shared_ptr.
Well, so far we have the overall understanding of the smart pointer. For more detail, such as how to create smart pointer, how to use, and how it facilitate the code, explore the code then.
1. Smart Pointers
//Smart pointers
#include <iostream>
#include <memory> //smart pointers
using namespace std;
class ThreeD {
public:
int ht;
int wid;
int dep;
ThreeD() { ht = wid = dep = 0; }
ThreeD(int i, int j, int k) { ht = i; wid = j; dep = k; }
};
class node {
public:
/*
node * next;
int value;
node() { next = nullptr; }
node(int i) { next = nullptr; value = i; }
*/
shared_ptr<node> next;
int value;
node() {}//all smart pointers will have initial default value nullptr
node(int i) { value = i; }
};
class linked_list {
public:
//node * head;
shared_ptr<node> head;
//linked_list() { head = nullptr; }
linked_list() {}
linked_list(const initializer_list<int>& V) {
const int * p = V.end() - 1;
while (p != V.begin() - 1) {
//node * p1 = new node(*it1);
shared_ptr<node> p1 = make_shared<node>(*p);
p1->next = head;
head = p1;
p--;
}
}
};
// ==========Function frequently uesed in smart pointer===============
// unique_ptr<T> up = make_unique<T>(10);
// up.reset() // clear the pointer of up
// shared_ptr<T> sp = make_shared<T>(10);
// weak_ptr<T> wp = sp;
// sp.use_count()
// wp.lock()
// wp.expired()
int main() {
//Raw pointers refer to the original pointers
//Three types of smart pointers: unique_ptr, shared_ptr, weak_ptr
unique_ptr<int> up1 = make_unique<int>(10);
up1.reset();// up1 becomes empty; object 10's ref count = 0 and will be deleted automatically.
//up1= nullptr;
up1 = make_unique<int>(15);
cout << *up1 << endl;//15
//unique_prt<int> up2 = up1; error! because the unique object can only be pointed by
//one unique pointer
shared_ptr<int> sp1 = make_shared<int>(20); //object 20's reference count is 1
shared_ptr<int> sp2 = sp1; //reference count is 2
shared_ptr<int> sp3 = make_shared<int>(30);
sp2 = sp3; //ref count for object 20 is 1
sp1 = sp3; //ref count for object 20 is 0 and the object 20 will be automatically deleted.
cout << sp1.use_count() << endl;//use_count returns reference count of the object pointed by sp1
shared_ptr<int> sp4;
weak_ptr<int> wp1;
wp1 = sp3; //ref count for object 30 remains 3; weak_ptr does not contribute to ref count
//sp4 = wp1; error! can't assign weak_ptr to shared_ptr
cout << sp3.use_count() << endl;//3
sp4 = wp1.lock();//lock() type "casting" weak_ptr to shared_prt
//lock returns a shard_ptr pointing to the object weak_ptr is pointing to
//if weak_ptr is nullptr, then a shared_ptr with value nullptr is returned.
cout << *sp1 << endl;
//cout << *wp1 << endl; Error! weak_ptr does not support * , ->
cout << *(wp1.lock()) << endl;
shared_ptr<ThreeD> sp5 = make_shared<ThreeD>(3, 4, 5);
weak_ptr<ThreeD> wp2 = sp5;
cout << sp5->ht << endl;
cout << wp2.lock()->ht << endl;
sp5.reset(); //the object (3,4,5) will be deleted
if (wp2.expired()) { cout << "Object has been deleted" << endl; }
//Reset funcitons are available to all three types of smart pointers
sp1.reset(new int(40));
sp1 = make_shared<int>(40);//this is faster than the above
//sp1.reset(make_shared<int>(40)); will not compile
//weak_ptr<int> wp3 = make_weak<int>(10); make_weak does not exist
weak_ptr<int> wp3 = make_shared<int>(99);//object 99 is created and immediately deleted because ref count is 0
cout << wp3.expired() << endl;//1
linked_list L1 = { 11,2,3,4,5 };
cout << L1.head->value << endl;
shared_ptr<linked_list> sp11 = make_shared<linked_list>(initializer_list<int>{ 66, 7, 8, 9, 10 });//notice the
//new syntax for initializer_list here
cout << sp11->head->value << endl;
getchar();
getchar();
return 0;
}
2. unique(), swap(), useCount()
//get_unique_swap_UseCount_SmartPointers
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main() {
int * p1 = new int(10);
shared_ptr<int> sp1(p1);//object 10 has a ref count = 1
sp1.reset(new int(20));//object 10 will be deleted even though it was
// pointed by p1; p1 in this case behaves like a weak_ptr
shared_ptr<int> sp2(new int(100));
shared_ptr<int> sp3 = make_shared<int>(100);//this is faster than the above
int * p2 = new int(50);
shared_ptr<int> sp4(p2);
if (sp4.get() == p2) cout << "p2 and sp4 point to the same objet" << endl;
//get() return the raw address
cout << *p2 << " " << *sp4 << endl;
shared_ptr<int> sp5 = sp4;
cout << sp5.use_count() << endl;//use_count() return ref count
cout << boolalpha << sp5.unique() << endl;
sp4.reset();
cout << noboolalpha << sp5.unique() << endl;
//unique() return true if ref count is 1;otherwise, false
//boolalpha and noboolalpha are flags which set printing mode
//for boolean value.
//boolalpha will have boolean values be printed as true or false
//noboolalpha will have boolean values be printed as 1 or 0
shared_ptr<int> sp6 = make_shared<int>(44);
shared_ptr<int> sp7 = make_shared<int>(55);
sp7.swap(sp6);
cout << *sp6 << " " << *sp7 << endl;
int * p10 = new int(25);
int * p11 = new int(35);
swap(p10, p11);
swap(sp6, sp7);//slower than sp6.swap(sp7);
cout << *p10 << " " << *p11 << " " << *sp6 << " " << *sp7 << endl;
getchar();
getchar();
return 0;
}
3. Doubly Linked List using Smart Pointer
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
template <class T> class ThreeD {
public:
T ht;
T wid;
T dep;
ThreeD() { ht = wid = dep = 0; }
ThreeD(T i) { ht = wid = dep = i; }
ThreeD(T a, T b, T c) { ht = a; wid = b; dep = c; }
T vol() const { return ht * wid*dep; }
bool operator==(const ThreeD<T> &t) const { return (vol() == t.vol()); }
template <class T> friend ostream & operator<<(ostream &s, const ThreeD<T> &t);
};
template <class T> class node {
public:
T value;
//node<T> * next;
shared_ptr<node<T>> next;
//node<T> * previous;
weak_ptr<node<T>> previous;
//node<T>() { next = nullptr; previous = nullptr; }
node<T>(){}
//node<T>(T v) { value = v; next = nullptr; previous = nullptr; }
node<T>(T v) { value = v; }
bool operator==(const node<T> &t) const { return value == t.value; }
};
template <class T> class linked_list {
public:
//node<T> * first;
//node<T> * last;
shared_ptr<node<T>> first, last;
//linked_list() { first = last = nullptr; }
linked_list() {}
linked_list(const initializer_list<T> &V);
void push_front(T t);
void push_back(T t);
bool operator==(const linked_list<T> &L) const;
linked_list(const linked_list<T> &L); //copy constructor
//~linked_list();//destructor
void operator=(const linked_list<T> &L);//L-value operator=
template <class T> friend ostream & operator<<(ostream &s, const linked_list<T> &L);
};
template <class T> linked_list<T>::linked_list(const initializer_list<T> &V) : linked_list() {
auto it1 = V.begin();
while (it1 != V.end()) {
push_back(*it1);
it1++;
}
}
/*
template <class T> linked_list<T>::~linked_list() { //destructor
node<T> * p;
while (first != nullptr) {
p = first->next;
delete first;
first = p;
}
}
*/
template <class T> linked_list<T>::linked_list(const linked_list<T> &L) : linked_list() { //copy constructor //num_nodes = 0;
//node<T> * p1 = L.first;
shared_ptr<node<T>> p1 = L.first;
//while (p1 != nullptr) {
while (p1) {
push_back(p1->value);
p1 = p1->next;
}
}
template <class T> void linked_list<T>::operator=(const linked_list<T> &L) { //operator= left ref
//node<T> * p;
shared_ptr<node<T>> p;
first.reset();
last.reset();
/*
while (first != nullptr) {
p = first->next;
delete first;
first = p;
}
*/
p = L.first;
//while (p != nullptr) {
while (p) {
push_back(p->value);
p = p->next;
}
}
template <class T> void linked_list<T>::push_front(T t) {
//node<T> * p = new node<T>(t);
//shared_ptr<node<T>> p(new node<T>(t)); this is OK
//shared_ptr<node<T>> p = new node<T>(t); Error!
shared_ptr<node<T>> p = make_shared<node<T>>(t);
//if (first == nullptr) { first = last = p; }
if (!first) { first = last = p; }
else {
p->next = first;
first->previous = p;
first = p;
}
}
template <class T> void linked_list<T>::push_back(T t)
{
//node<T> * p = new node<T>(t);
shared_ptr<node<T>> p = make_shared<node<T>>(t);
//if (first == nullptr) { first = last = p; }
if (!first) { first = last = p; }
else {
p->previous = last;
last->next = p;
last = p;
}
}
template <class T> bool linked_list<T>::operator==(const linked_list<T> &L) const {
int n1 = 0, n2 = 0;
//node<T> * p;
shared_ptr<node<T>> p;
p = first;
//while (p != nullptr) {
while (p) {
p = p->next;
n1++;
}
p = L.first;
//while (p != nullptr) {
while (p) {
p = p->next;
n2++;
}
if (n1 != n2) return false;
//node<T> * p1 = first, *p2 = L.first;
shared_ptr<node<T>> p1 = first, p2 = L.first;
//while (p1 != nullptr) {
while (p1) {
if (!(p1->value == p2->value)) { return false; }
p1 = p1->next;
p2 = p2->next;
}
return true;
}
template <class T> ostream & operator<<(ostream &s, const ThreeD<T> &t) {//ostream & operator<<(ostream &s, ThreeD t) will also work.
s << "( " << t.ht << ", " << t.wid << ", " << t.dep << " )";
return s;
}
template <class T> ostream & operator<<(ostream &s, const linked_list<T> &L) {
//node<T> * p = L.first;
shared_ptr<node<T>> p = L.first;
//while (p != nullptr) {
while (p) {
s << p->value << " ";
p = p->next;
}
return s;
}
template <class T> ostream & operator<< (ostream &s, const vector<T> & V) {
s << "[";
for (size_t i = 0; i < V.size() - 1; i++) {
s << V[i] << ", ";
}
s << V[V.size() - 1] << "]";
return s;
}
int main() {
ThreeD<int> td3(3), td4(4), td5(5), td6(6), td7(100, 200, 300);
linked_list<string>ls_1;
ls_1.push_front("David");
ls_1.push_front("John");
ls_1.push_front("Pat");
ls_1.push_front("Ben");
ls_1.push_front("Jeff");
cout << ls_1 << endl;
linked_list<string>ls_2;
ls_2.push_front("Wendy");
ls_2.push_front("Mary");
ls_2.push_front("Nancy");
ls_2.push_front("Jennifer");
cout << ls_2 << endl;
ThreeD<double> t10(3.2, 7.4, 8.9), t11(5.6, 7.7, 2.987), t12(4.6, 7.5, 3.1416), t13(55.6, 66.8, 333.45);
linked_list<ThreeD<double>> LTD1;
LTD1.push_front(t10);
LTD1.push_front(t11);
linked_list<ThreeD<double>> LTD2;
LTD2.push_front(t13);
LTD2.push_front(t12);
LTD2.push_front(t10);
LTD2.push_front(t11);
cout << LTD2;
vector<linked_list<ThreeD<int>>> V1 = {
{ { 1,2,3 },{ 4,5,6 } },{ { 11,12,13 },{ 14,15,16 },{ 3,4,5 } },
{ { 7,8,9 },{ 2,2,3 },{ 4,4,4 } }
};
cout << V1 << endl;
getchar();
getchar();
return 0;
}
4. Ring using Smart Pointer
It highlights the advantage of smart pointer when we destruction the object.
#include <iostream>
#include <memory>
using namespace std;
template <typename T> class Node {
public:
shared_ptr<T> pValue;
shared_ptr<Node<T>> next;
Node() {}
Node(T v) { pValue = make_shared<T>(v); }// pValue.reset(new T(v)); slower
};
template <typename T> class Ring {
public:
shared_ptr<Node<T>> head;
//Node<T>* head;
Ring() {};//Ring() = default;
//Implement the following functions:
Ring(const initializer_list<T>& I); //initializer_list
~Ring();//destructor
Ring(const Ring<T>& R);//copy constructor
void operator=(const Ring<T>& R);//Lvalue operator=; copy assignment
Ring(Ring<T>&& R);//move constructor
void operator=(Ring<T>&& R);//Rvalue operator=; move assignment
Ring<T> ThreeTimes();
};
template <class T> Ring<T>::Ring(const initializer_list<T>& I) { //initializer_list
shared_ptr<Node<T>> p2;
auto p = I.end() - 1;//const T * p = ...
while (p != I.begin() - 1) {
shared_ptr<Node<T>> p1 = make_shared<Node<T>>(*p);
p1->next = head;
head = p1;
if (p == I.end() - 1) p2 = p1;//p2 will point to the last node of Ring
p--;
}
p2->next = head;//form the ring
cout << "Initializer_list" << endl;
}
template <class T> Ring<T>::~Ring() { //destructor
if (!head) {
cout << "Destrutor" << endl;
return;
}
head->next.reset();
cout << "Destrutor" << endl;
}
template <class T> Ring<T>::Ring(const Ring<T>& R) { //copy constructor
shared_ptr<Node<T>> p1, p2, p3;
if (!R.head) return;
p1 = make_shared<Node<T>>();
p1->next = head;
head = p1;
p3 = p1;
p2 = R.head->next;
while (p2 != R.head) {
p1 = make_shared<Node<T>>();
p1->next = head;
head = p1;
p2 = p2->next;
}
p1 = R.head;
p2 = head;
while (p2) {
p2->pValue = make_shared<T>(*p1->pValue);
p1 = p1->next;
p2 = p2->next;
}
p3->next = head;
cout << "Copy Constructor" << endl;
}
template <class T> void Ring<T>::operator=(const Ring<T>& R) { //Lvalue assignment, i.e., copy assignment
if (head) {
head->next.reset();
head.reset();//wihtout this, the first node will not be deleted.
}
shared_ptr<Node<T>> p1, p2, p3;
if (!R.head) return;
p1 = make_shared<Node<T>>();
p1->next = head;
head = p1;
p3 = p1;
p2 = R.head->next;
while (p2 != R.head) {
p1 = make_shared<Node<T>>();
p1->next = head;
head = p1;
p2 = p2->next;
}
//p2->next = head;
p1 = R.head;
p2 = head;
while (p2) {
p2->pValue = make_shared<T>(*p1->pValue);
p1 = p1->next;
p2 = p2->next;
}
p3->next = head;
cout << "Copy Assignment" << endl;
}
template<class T> Ring<T>::Ring(Ring<T>&& R) { //Move constructor
head = R.head;
R.head.reset();
cout << "Move Constructor" << endl;
}
template<class T> void Ring<T>::operator=(Ring<T>&& R) {//Rvalue assignment; move assignment
if (head) {
head->next.reset();
head.reset();
}
head = R.head;
R.head.reset();
cout << "Move Assignment" << endl;
}
template<class T> Ring<T> Ring<T>::ThreeTimes() {
Ring<T> temp = *this;// copy consturctor
if (!head) return temp;
shared_ptr<Node<T>> p1 = temp.head, p2;
*(p1->pValue) *= 3;
p2 = p1->next;
while (p2 != temp.head) {
*p2->pValue *= 3;
p2 = p2->next;
}
return temp;//return move(temp); return by value; move constructor
//a temporary (or hidden) copy is created
//destructor to remove temp
}
template <class T> ostream& operator<<(ostream& str, const Ring<T>& R) {
if (!R.head) return str;
str << *R.head->pValue << " ";
shared_ptr<Node<T>> p1 = R.head->next;
while (p1 != R.head) {
str << *p1->pValue << " ";
p1 = p1->next;
}
return str;
}
int main() {
Ring<int> R1{ 10,20,30,40,50 };//initializer_list
//cout << *R1.head->pValue << endl;
cout << R1 << endl;
Ring<int> R2{ R1 };//copy constructor
cout << R2 << endl;
Ring<int> R3;
R3 = R1;//copy assignment
cout << R3 << endl;
Ring<int> R4;
R4 = R1.ThreeTimes();//this is pointing to object R1
//Compiler will convert it to R4.operator=(R1.ThreeTimes());
//copy constructor -- declare temp and initialize it to R1
//move constructor -- create hidden (temporary) copy
//destructor-- delete temp becuase it goes out of scope
//move assignment -- Assign value from hidden copy to R4
//destructor-- delete hedden copy because it goes out of scope
cout << R4 << endl;
return 0;
}
Extra: const snippet
int i = 10;
//can be re-written as
//int const * p = &i;
const int * p = &i;
//*p = 100; Error! the object pointed by p is const
//the above can be re-written as int const * p = &i;
i = 250;
//====================================================================================
int * const p = &i; //pointer p is const, but not the object pointed by p
*p = 1000;
int k = 20;
//p= &k; error. p is const
const int * const p = &k; //both pointer and the object pointed by the pointer are const