In this lecture on modern C++, we briefly explain the smart pointer called unique_prt and we explain how to use it in C++ code. We do not plan to cover all the caveats of smart pointers, but instead, we focus on the basic concepts that will explain you how to use them. The YouTube tutorial thoroughly explaining the unique_ptr is given below.
First, let us explain the need for smart pointers.
THE NEED FOR SMART POINTERS
When allocating (reserving) the memory on heap using “raw” (standard) pointers, we need to manually deallocate (free) this memory by calling “delete”. However,
- We can forget to include a delete statement in the code
- An exception or an execution error might occur before the delete statement
In these and similar cases, we create memory leaks that can lead to instabilities. TO AVOID THIS AND SIMILAR PROBLEMS WE USE SMART POINTERS!
Below, is a C++ code that presents an example with a memory leak.
#include <iostream>
#include<string>
using namespace std;
class Student{
public:
Student()
{
cout<<"Default constructor called!"<<endl;
}
Student(string name)
{
cout<<"Constructor called!"<<endl;
studentName=name;
}
~Student()
{
cout<<"Destructor called!"<<endl;
}
void getName()
{
cout<<"Student name is:"<<studentName<<endl;
}
void setName(string name)
{
studentName=name;
}
private:
string studentName;
};
void manipulationFunction()
{
// Student object created on the heap
// the pointer pStudent is on stack
Student* pStudent= new Student();
pStudent->setName("Janne Doe");
pStudent->getName();
//destructor never called!
}
int main()
{
cout<<"Calling the function (standard pointer)"<<endl;
manipulationFunction();
return 0;
}
Here, in the function manipulationFunction() we are creating a memory leak. This is done by reserving a memory space on the heap by using the new operator. The memory address is returned to the pointer pStudent. This pointer is defined on the stack of the function and goes out of scope when the function returns. Since we did not call the delete operator, we lose track of the reserved memory space and create a memory leak.
Modern C++ strongly discourages the use of raw pointers!
In modern C++ books, you will often see something along these lines:
To avoid problems with memory leaks and similar problems, the modern C++ programming language strongly discourages the use of raw (standard) pointers except for some cases. Instead, programs and programmers are encouraged to use smart pointers.
Explanation of Stack and Heap Memory Storages
In order to properly understand smart pointers, we first need to understand heap and stack memory storages. In C++, heap and stack are two memory regions.
Memory on stack is automatically managed by the compiler. This means that the memory is automatically allocated and deallocated. Typically, local variables, function parameters, and return addresses are stored on stack. When we call a function, memory space for its variables and other function objects is automatically allocated (reserved) on the stack. This memory is automatically deallocated (freed) when the function returns or ends! That is, the local variables inside of the function go out of the scope.
Consider this example:
int function1(int a, int b)
{int c;
c=a+b;
return c;
}
Here, memory to store c is allocated on the stack. When the function returns the value, the memory and stored value are deallocated (erased). The following two images explain more thoroughly how stack is organized.
On the other hand, the heap is completely differently organized and managed. The heap space can dynamically grow and shrink during the program execution (during runtime). Consider the following example
int* p = new int;
*p = 10;
Here, memory to store p is allocated on the heap. To free this memory we need to call delete:
delete p
However, since variables in a function are located on a stack, and stack is automatically cleared, we run into memory leak issues if we do not call the delete operator as illustrated in the example shown below.
void function1(int a, int b)
{
int* p = new int;
*p = a+b;
cout<<*p<<endl;
}
To fix this code, write this
void function1(int a, int b)
{
int* p = new int;
*p = a+b;
cout<<*p<<endl;
delete p;
}
However, here, we can run into another issue. What happens if the delete line exists, however, due to some exception or an error, the code never executes this line?
void function1(int a, int b)
{
int* p = new int;
*p = a+b;
cout<<*p<<endl;
// CODE BLOCK THAT THROWS AN EXCEPTION...
delete p;
}
Solution: Use Smart Pointer unique_ptr
The solution to these and similar problems is to use the smart pointer unique_ptr (there are also others). To use the smart pointers, we need the header file called “memory”:
#include <memory>
Here is a previous example rewritten by using the unique_ptr smart pointer.
#include<iostream>
#include<memory>
using namespace std;
void function2(int a, int b)
{
unique_ptr<int> p(new int);
*p = a+b;
cout<<*p<<endl;
}
Another example is shown below.
#include<iostream>
#include<memory>
using namespace std;
void function3(int a, int b)
{
unique_ptr<int> p=make_unique<int>(0);
*p = a+b;
cout<<*p<<endl;
}
We can also use smart pointer on classes and other objects.
class Student{};
int main()
{
unique_ptr<Student> pStudent(new Student());
// unique_ptr<Student> pStudent=make_unique<Student>();
}
A smart pointer is a class template that is declared on the stack and initialized by using a pointer that points to the memory on heap. The smart pointer will automatically delete the memory that the raw pointer points to. That is, we do not need to use the delete operator! Inside of the destructor (smart pointer is a class template!) of the smart pointer there is a call to delete that releases the memory!
Since the smart pointer is declared on stack, its destructor is automatically called when the smart pointer goes out of scope, even if an error or an exception is thrown somewhere. This means that it is guaranteed that the memory is deleted. The philosophy of smart pointers is to give control (give ownership) of any memory resource reserved on heap to an object (smart pointer) on stack. The destructor of the object on the stack contains procedure to free the memory once the object goes out of scope.
Finally, below is the example from the beginning of the tutorial explaining how to use the smart pointer in order to fix the memory leak problem.
#include <iostream>
#include<string>
// to use smart pointers, we need to include this header file
#include<memory>
using namespace std;
class Student{
public:
Student()
{
cout<<"Default constructor called!"<<endl;
}
Student(string name)
{
cout<<"Constructor called!"<<endl;
studentName=name;
}
~Student()
{
cout<<"Destructor called!"<<endl;
}
void getName()
{
cout<<"Student name is:"<<studentName<<endl;
}
void setName(string name)
{
studentName=name;
}
private:
string studentName;
};
void manipulationFunction()
{
// Student object created on the heap
// the pointer pStudent is on stack
Student* pStudent= new Student();
pStudent->setName("Janne Doe");
pStudent->getName();
//destructor never called!
}
void manipulationFunctionSP()
{
unique_ptr<Student> sPStudent(new Student());
sPStudent->setName("John Best");
sPStudent->getName();
//destructor never called!
}
void manipulationFunctionSP2()
{
unique_ptr<Student> sPStudent= make_unique<Student>();
sPStudent->setName("John S");
sPStudent->getName();
//destructor never called !
}
int main()
{
Student student;
student.setName("John Doe");
student.getName();
cout<<"Calling the function (standard pointer)"<<endl;
manipulationFunction();
cout<<"Out of the function (standard pointer)"<<endl;
cout<<"Calling the functions (smart pointer)"<<endl;
manipulationFunctionSP();
manipulationFunctionSP2();
cout<<"Out of the function (smart pointer)"<<endl;
return 0;
}
