In this article, we will discuss when and how to use a custom deleter with shared_ptr
in Modern C++.
Table of Contents
Using shared_ptr with Arrays in C++
When a shared_ptr
object goes out of scope, its destructor is indeed called. Within its destructor, the reference count is decremented by one. If the new value of the reference count reaches zero, then it proceeds to delete the associated raw pointer.
By default, a shared_ptr
calls the delete
operator to deallocate the memory, like so:
delete pointer;
However, it’s not always appropriate to use the delete
operator for destruction. There can be different requirements, such as when the shared_ptr
is managing a dynamically allocated array instead of a single object.
Here is an incorrect example where shared_ptr
is used with a raw array:
std::shared_ptr<int> p3(new int[12]);
In this scenario, when p3
goes out of scope, the shared_ptr
will try to delete the array using the delete
operator. However, this is incorrect because the delete
operator is intended for single objects, not arrays. The proper way to delete an array is with the delete[]
operator, which deallocates memory allocated for arrays.
To correctly manage an array with a shared_ptr
, you should provide a custom deleter that uses delete[]
, like this:
Frequently Asked:
- Smart Pointer vs Raw Pointer in C++
- Shared_ptr & Custom Deleter in Modern C++
- What is shared_ptr in C++?
- How not to use Smart Pointers in C++?
std::shared_ptr<int> p3(new int[12], std::default_delete<int[]>());
Alternatively, for arrays, you can use std::unique_ptr
with a special array form, which handles arrays correctly:
std::unique_ptr<int[]> p4(new int[12]);
It’s important to note that since C++17, we can use std::make_shared
and std::make_unique
for arrays as well, which provide a safer and more convenient way of handling dynamic arrays:
// For shared_ptr with arrays (C++17 and later) auto p5 = std::make_shared<int[]>(12); // For unique_ptr with arrays auto p6 = std::make_unique<int[]>(12);
In these examples, p5
and p6
will correctly handle the deletion of the array when they go out of scope, ensuring that delete[]
is used instead of delete
. Using std::make_shared
and std::make_unique
also avoids the need to specify a custom deleter, as they correctly deduce the array type and use the appropriate deletion mechanism.
Using shared_ptr with Custom Deleter
Using a Normal Function as Custom Deleter in shared_ptr
Adding a custom deleter to a shared_ptr
is a flexible way to control the destruction of the managed object when the last shared_ptr
owning it goes out of scope.
#include <iostream> #include <memory> struct Sample { Sample() { std::cout << "Sample CONSTRUCTOR\n"; } ~Sample() { std::cout << "Sample DESTRUCTOR\n"; } }; // Function that calls 'delete[]' on the received pointer void deleter(Sample* x) { std::cout << "Custom DELETER FUNCTION CALLED\n"; delete[] x; } int main() { // Creating a shared_ptr with custom deleter std::shared_ptr<Sample> p3(new Sample[12], deleter); return 0; }
Output:
Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Custom DELETER FUNCTION CALLED Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR
In the above code:
- The
Sample
struct has a constructor and destructor which print messages when they’re called. - The
deleter
function is a custom deleter function that we want to use instead of the defaultdelete
operator to destroy an array. It prints a message when called and then usesdelete[]
to free the array. - The
shared_ptr
namedp3
is created with an array ofSample
objects and the customdeleter
function. - When
p3
goes out of scope at the end ofmain
, the custom deleter is called, which deletes the array ofSample
objects and then the destructor for eachSample
object is called.
One key point to note is that the constructors and destructors will be called for each object in the array. This means that you will see the constructor message 12 times, once for each element of the array, and similarly, when the array is deleted, the destructor message will be printed 12 times.
Also, it’s worth noting that while this method of managing arrays works well, it’s generally recommended to use std::vector
or std::array
for array management unless there’s a specific need for dynamic array allocation with raw pointers. These standard library containers handle memory management automatically and safely.
Using a Lambda Function as Custom Deleter in shared_ptr
Using a custom deleter with a std::shared_ptr
is indeed a powerful feature of C++ smart pointers that allows for greater control over the destruction of managed objects. As a custom deleter we can either use a function object or a lambda function, as demonstrated in the below example,
#include <iostream> #include <memory> struct Sample { Sample() { std::cout << "Sample CONSTRUCTOR\n"; } ~Sample() { std::cout << "Sample DESTRUCTOR\n"; } }; int main() { // Creating a shared_ptr with a lambda function as the custom deleter std::shared_ptr<Sample> p4( new Sample[12], [](Sample *x) { std::cout << "Lambda Function DELETER CALLED\n"; delete[] x; }); // No need to manually delete; it's handled by the custom deleters return 0; }
Output:
Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Sample CONSTRUCTOR Lambda Function DELETER CALLED Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR Sample DESTRUCTOR
Summary
Today we learned when and how to use a custom deleter with shared_ptr
in C++.
Ambiguous topic cleared in a nice way…Thank you.
awesome