Introduction
The concept of pointers is used for accessing the resources external to the program or the memory. Any variable or resource we use could be just a copy of the resource. The changes we make to it affect only the copied version and not the original one. A pointer can store the original resource's location for the change to be reflected. C and C++ use pointers to perform other operations such as allocating new objects on the heap, passing functions to other functions and iterating over the elements of data structures. Also see, Literals in C.Fibonacci Series in C++
Smart pointers
In C/C++, deallocating a pointer can cause a memory leak that may crash the program. A special category of pointers ensures that programs are free of memory and resource leaks and are safe from exceptions. To deallocate unused memory and free the destroyed object’s memory, Smart pointers are used. In this blog, we shall discuss the different types of Smart pointers in C++.
-
auto_ptr (deprecated)
-
unique_ptr
-
shared_ptr
-
weak_ptr
These pointers are defined in the std namespace and the “memory” header file.
auto_ptr
The auto_ptr class template is deprecated as of C++11. It is similar to unique_ptr, which has improved security, array support and other additional features. auto_ptr provides a limited garbage collection facility for pointers. It allows pointers to automatically destroy the elements when the object itself is destroyed.
If two auto_ptr objects happen to own the same element, both will attempt to destroy them at some point. Auto_ptr is based on an exclusive ownership model where two pointers of the same type can’t point to the same resource simultaneously. It transfers ownership of the stored value assigned to another object.
The below example shows two auto_ptr of the same type. Initially, x holds an address which is later copied to y. This causes x to become Null. auto_ptr y, which now holds the address, is passed as an object to a function named funcA. When the function returns, the contents of y are also changed to NULL.
The get() function returns a standard pointer to an object based on the auto_ptr.
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
void display() { }
};
void funcA(auto_ptr<A> x) { }
int main()
{
// x is an auto_ptr of type A
auto_ptr<A> x(new A);
cout << "x" << endl;
// return address of x
cout << "Before copy " << x.get() << endl;
// copying makes x empty.
auto_ptr<A> y(x);
// x is empty now
cout << "After copy " << x.get() << endl;
cout << "y"<< endl;
// x gets copied in y
cout << "Before func call " << y.get() << endl;
// function call to pass y
funcA(y);
// y is now empty
cout << "After func call " << y.get() << endl;
return 0;
}
Output:
x
Before copy 0x15ece70
After copy 0
y
Before func call 0x15ece70
After func call 0
This shows that two auto_ptr objects will not try to delete the same pointer. Deleting a pointer twice is not a valid operation and can crash the program, whereas deleting NULL is valid and possible.
unique_ptr
The unique_ptr is a smart pointer that owns and manages another object via a pointer. It disposes of the object using the associated deleter when unique_ptr goes out of scope. Out of scope means that either the unique_ptr object is destroyed or is assigned another pointer. The default deleter destroys the object and deallocates the memory using the delete operator. As mentioned before, it is an improved version of auto_ptr. However, unlike auto_ptr, unique_ptr can be transferred but not copied or shared.
Unique_ptr can manage a single object or a dynamically-allocated array of objects. It can quickly store and retrieve data from Collections in C++. Usually, it is used to manage the lifetime of objects. This may include acquiring and passing ownership of objects with dynamic lifetime into functions and providing safety from exceptions. It guarantees deletion on normal exits and exits due to exceptions.
Declaration:
unique_ptr<T> ptr(raw);
where,
ptr – pointer of unique_ptr type,
raw – raw pointer to type T,
T is the type of value pointed to by ptr.
It has three main functions:
-
get() returns a standard pointer to an object based on the unique_ptr pointer without transferring its ownership.
-
release() returns a standard pointer based on the unique_ptr pointer with the transfer of ownership.
-
reset() resets the ownership of the object. The unique_ptr pointer's value is changed to NULL.
In the below example, two unique_ptr are of the same type. Initially, x holds an address which is later transferred to y. This causes x to become Null. unique_ptr y now holds the address. A function fun containing a new unique_ptr is called and returned. The returned value is then transferred to y.
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
void display() {}
};
unique_ptr<A> fun()
{
unique_ptr<A> ptr(new A);
cout << "ptr: " << ptr.get() << endl;
return ptr;
}
int main()
{
unique_ptr<A> x(new A);
cout << "x" << endl;
// returns address of a
cout << "Before Transfer" << x.get() << endl;
// transferring ownership
unique_ptr<A> y = move(x);
// x is empty
cout << "After Transfer " << x.get() << endl;
cout << "y" << endl;
cout << "Before Call " << y.get() << endl;
// calling fun()
y = move(fun());
cout << "After call " << y.get() << endl;
return 0;
}
Output:
x
Before Transfer 0xb51e70
After Transfer 0
y
Before call 0xb51e70
ptr 0xb52ea0
After call 0xb52ea0
shared_ptr
The shared_ptr pointer shares the ownership of an object while storing a pointer to another object. It uses the reference counting ownership model, which maintains the reference count of the pointer with all copies of the shared_ptr. Reference counting is a way of sorting the number of references, disk space, memory block and other resources. A counter is incremented every time a new pointer points to the resource and decremented when the destructor of an object is called.
An object referenced by the raw pointer is not destroyed until the reference count exceeds zero. A reference count greater than zero implies that all copies of shared_ptr have been deleted.
Shared_ptr is used to point to member objects while owning the object they belong to. Multiple threads on different instances of shared_ptr can be called without additional synchronisation, even if the instances are copies.
Declaration:
shared_ptr<T> ptr;
shared_ptr<T> ptr(raw);
where
ptr refers to the name of the shared_ptr,
T denotes the data type of the pointer it points to,
raw is a pointer to data of type T.
The methods supported by shared_ptr pointer:
-
get() returns a pointer based on the shared_ptr pointer without transferring rights to the object.
-
swap() swaps the values of two pointers.
-
reset() resets the ownership of the object.
-
use_count() returns the number of copies of the object pointers.
The below example shows two auto_ptr of the same type. Initially, x holds an address which is later shared with y. The number of copies of their object pointers is calculated. This returns 2 as there are two copies of the pointer. X is then reset and becomes empty. Now, the number of copies of object pointers reduces to 1.
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
void display() {}
};
int main()
{
shared_ptr<A> x(new A);
cout << "x Before share " << x.get() << endl;
// sharing
shared_ptr<A> y(x);
cout << "x After share " << x.get() << endl;
cout << "y After share " << y.get() << endl;
// Return the number of shared_ptr objects
cout << "x no of object pointers " << x.use_count() << endl;
cout << "y no of object pointers" << y.use_count() << endl;
// Reset ownership of x
x.reset();
cout << "x After reset " << x.get() << endl;
cout << "y After reset no of object pointers" << y.use_count() << endl;
cout << "y After reset " << y.get() << endl;
return 0;
}
Output:
x Before share 0x10e6e70
x After share 0x10e6e70
y After share 0x10e6e70
x no of object pointers 2
y no of object pointers 2
x After reset 0
y After reset no of object pointers 1
y After reset 0x10e6e70
You practice by yourself with the help of online c++ compiler.
weak_ptr
weak_ptr holds a weak reference to an object managed by the shared_ptr. It must be converted to shared_ptr to access the referenced object. Thus, weak_ptr is created as a copy of shared_ptr. weak_ptr implements temporary ownership and does not own the object it points to. It tracks the object, which is then converted to shared_ptr to assume temporary ownership.
weak_ptr can also break reference cycles that are formed by the objects managed by shared_ptr. This helps solve Cyclic Dependency Problems with shared_ptr. If two classes A and B point to each other using a shared_ptr, the use_count will never reach zero, and they never get deleted. weak_ptr can prevent this problem as they are not reference counted. The shared_ptr of class A can be declared as a weak_ptr declaring A_ptr as weak_ptr. Hence, class A does not own the object and can only access it.
To change the object a weak_ptr pointer points to, it is converted into a shared_ptr pointer using the lock() method. If the original shared_ptr gets destroyed, then the object's lifetime continues until the temporary shared_ptr is destroyed.
Declaration:
shared_ptr<T> shared(raw);
weak_ptr<T> weak = shared;
where
raw – a raw pointer pointing to data of type T,
shared – a pointer of shared_ptr type,
weak – a pointer of weak_ptr typ,
T – data type pointed to by raw, shared, weak pointers.
The methods defined for weak_ptr are:
-
lock() converts a pointer of weak_ptr type into shared_ptr type.
-
swap() swaps weak_ptr type pointers.
In the following example, two shared_ptrs are created along with two weak_ptrs. The weak_ptrs are then swapped with each other to acquire a resource. Next, we count the number of elements weak_ptr can access. Initially, weak_ptr_1 can access only the object pointed by shared_ptr_2. After the creation of shared_ptr_3, using shared_ptr_2, weak_ptr can now access two object pointers.
#include <memory>
#include <iostream>
int main()
{
// shared_ptr
std::shared_ptr<int> shrd_ptr_1(new int(8));
std::shared_ptr<int> shrd_ptr_2(new int(10));
// weak_ptr
std::weak_ptr<int> wk_ptr_1(shrd_ptr_1);
std::weak_ptr<int> wk_ptr_2(shrd_ptr_2);
std::cout << "Before swap wk_ptr_1 = " << *wk_ptr_1.lock() << std::endl;
std::cout << "Before swap wk_ptr_2 = " << *wk_ptr_2.lock() << std::endl;
// swapping weak_ptr_1 and weak_ptr_2
swap(wk_ptr_1, wk_ptr_2);
std::cout << " After swap wk_ptr_1 = " << *wk_ptr_1.lock() << std::endl;
std::cout << "After swap wk_ptr_2 = " << *wk_ptr_2.lock() << std::endl;
std::cout << "Initial count the number_of_weak_ptr : " << wk_ptr_1.use_count() << std::endl;
std::shared_ptr<int> shrd_ptr_3(shrd_ptr_2);
std::cout << "Final count the number_of_weak_ptr : " << wk_ptr_1.use_count() << std::endl;
return (0);
}
Output:
Before swap wk_ptr_1 = 8
Before swap wk_ptr_2 = 10
After swap wk_ptr_1 = 10
After swap wk_ptr_2 = 8
Initial count the numner_of_weak_ptr : 1
Final count the number_of_weak_ptr : 2