Shared_ptr & Custom Deleter in Modern C++

In this article, we will discuss when and how to use a custom deleter with shared_ptr in Modern C++.

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:

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 default delete operator to destroy an array. It prints a message when called and then uses delete[] to free the array.
  • The shared_ptr named p3 is created with an array of Sample objects and the custom deleter function.
  • When p3 goes out of scope at the end of main, the custom deleter is called, which deletes the array of Sample objects and then the destructor for each Sample 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++.

2 thoughts on “Shared_ptr & Custom Deleter in Modern C++”

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top