# Custom Deleter for C++ Smart Pointers

## Introduction

We sometimes see custom deleters used for C++ smart pointers. Previously, I have not thought too much about why and when we have to use custom deleters, and what the efficient way to include custom deleters is.

In this blog post, I would like to discuss the efficient usages and motivations of custom deleters for C++ std::unique_ptr and std::shared_ptr.

## Discussions

### When do we need custom deleters for smart pointers?

Usually it is when we need to use the objects from some C libraries for our application. Objects from C library do not have destructor and usually requires specific resource free functions to free the memory allocated on heap. If all the objects are from C++ libraries and have well-defined destructor, we can just create std::unique_ptr and std::shared_ptr without deleters.

### Why don’t std::make_unique and std::make_shared accept custom deleters?

std::make_unique and std::make_shared do not accept custom deleters. Their purposes are mostly for replacing the new/delete behaviors for C++ objects. If we ever have to use custom deleters, we use std::unique_ptr and std::shared_ptr instead.

### Why std::unique_ptr carries deleter type as its part of type whereas std::shared_ptr does not?

Most likely it is because of the performance. std::shared_ptr always carries control block to track the object sharing status and is thus less efficient. Creating a copy of an additional custom deleter will not likely affect its size and performance significantly. std::unique_ptr, however, is designed for more efficient purposes and tries to be as efficient as the raw pointer. With a stateless deleter and possibly compile-time empty base optimization, the deleter code could be inlined and there is no additional memory required to store the deleter, therefore maintaining the performance of std::unique_ptr. For example, std::FILE*, std::unique_ptr<std::FILE>, and std::unique_ptr<std::FILE, FileCloser> are all 8 Bytes. So using stateless functor deleter for std::unique_ptr is as efficient as the raw pointer. However, if the optimization fails to be applied, the size of std::unique_ptr could go arbitrarily large as the deleter could have state and go arbitrarily large. std::shared_ptr data structure only has the raw pointer and a pointer to the control block, so its size is always fixed and usually twice as large as std::unique_ptr.

### What is the control block for std::shared_ptr?

The control block is a dynamically-allocated object that holds:

• either a pointer to the managed object or the managed object itself;
• the deleter (type-erased);
• the allocator (type-erased);
• the number of std::shared_ptrs that own the managed object;
• the number of std::weak_ptrs that refer to the managed object.

The reference counts update uses atomic instructions which brings some overhead when std::shared_ptr gets copied. However, the dereferencing cost are exactly the same as the raw pointer.

Lei Mao

07-27-2021

02-27-2022