C++ Notes to Self: Unique Pointers
When programming in C++ there’s a geniune risk of forgetting to release dynamically allocated memory. To address this issue, the Standard Library promotes the idea of resource aquistion is initialization that underlies the use of smart pointers. C++11 provides three types of smart pointers. One of these is the unique pointer. We use a unique pointer whenever the underlying raw pointer it encapsulates doesn’t have to be shared with other parts of the code.
Unique pointers belong to the std
namespace and we need to include <memory>
beforehand. A unique pointer is a template class, meaning we have to indicate the pointer type in the declaration statement. Because a unique pointer has an explicit constructor, we can not initialize it through an assignment. Instead we initialize it directly.
std::unique_ptr<Integer> p_i01{new Integer(43)};
std::unique_ptr<Integer> p_i02{getIntegerPtr(222)};
Note that a unique pointer is overloaded for assignment and comparison, allowing it to be set or compared to nullptr
. In the same way, the arrow operation (->
) is also overloaded. We use this idiom to access the member functions of the underlying raw pointer.
Unique pointers require added operations to carry out reassignment. After it has been declared and set to an initial value, we need to call the reset()
member function to replace the value of the underlying object. First reset()
deletes the presenty assigned object, then it sets the underlying pointer to a new object, the parameter we pass to the method. In the second case shown below, a delete
is called on the underlying pointer, releasing its object and resetting the raw pointer to nullptr
.
The exception to this requirement is an assignment to a nullptr
. In that case the unique pointer has been overloaded to behave much like a raw pointer. Behind the scenes the Standard Library releases the object set to the underlying raw pointer and reassigns the later to nullptr
.
Can a unique pointer be copied? No. We can’t involve it in copy constructors or copy assignments.
Can it be moved? Yes. We can pass a unique pointer as a parameter to std::move()
. For the same reasons, we can pass it to a move constructor or a move assignment.
Still on the topic of overloaded operators, the dereferencing operatior (*
) is also overloaded. It returns the address of the underlying pointer.
Bear in mind that unique pointers are not raw pointers; they are objects. Therefore we can’t call delete
on them. We also can not pass a unique pointer as a function parameter in place of an object pointer. Instead we use the unique pointer’s get()
member function to retrive the underlying pointer which we then pass as a parameter to the function call.
Taken all together, unique pointers obviate the need to call the delete
operator. When a unique pointer goes out of scope, the Standard Library cleans up dynamic memory accordingly. As we use the reset()
operation, or assign a unique pointer to nullptr
, the Standard Library likewise takes care of the necessary memory management behind the scenes, greatly simplifying the task of programming C++.
#include <memory>
#include <cassert>
#include "Integer.h"
Integer* getIntegerPtr(const int val) {
Integer* retVal = new Integer(val);
return retVal;
}
void showPtrAddress(const Integer* const p) {
std::cout << "Memory address: " << p << std::endl;
}
void showData(const std::unique_ptr<Integer>& p) {
std::cout << p->GetValue() << std::endl;
}
int main(int argc, char** argv) {
Integer* p_i01 = getIntegerPtr(432);
std::cout << p_i01->GetValue() << std::endl;
*p_i01 = __LINE__;
std::cout << "Line #: " << p_i01->GetValue() << std::endl;
showPtrAddress(p_i01);
std::unique_ptr<Integer> p_i02{getIntegerPtr(222)};
assert(p_i02 != nullptr);
showData(p_i02);
p_i02.reset(new Integer(45));
showData(p_i02);
*p_i02 = __LINE__;
std::cout << "Line #: " << p_i02->GetValue() << std::endl;
showPtrAddress(p_i02.get());
return 0;
}
Listing for main.cpp.