In C++, managing heap memory manually using raw pointers is a frequent source of bugs. Modern C++ solves this by introducing smart pointers. The simplest and most efficient smart pointer is std::unique_ptr (defined in the <memory> header), which implements the concept of exclusive ownership.
Let's look at unique pointers, copy constraints, and how to write custom deleters for non memory resources.
Exclusive Ownership: One Owner Only
A std::unique_ptr exclusively owns the resource it points to. Because ownership is unique, copying a unique pointer is explicitly blocked by the compiler (its copy constructor is deleted). If you were allowed to copy it, two unique pointers would point to the same memory, and both would try to delete it when exiting scope, causing a double free crash.
To transfer ownership of the resource to another unique pointer, you must use move semantics:
Zero Runtime Overhead
At the physical machine level, a std::unique_ptr is simply a wrapper around a raw pointer. It contains exactly one pointer member. This means the size of a std::unique_ptr in memory is exactly 8 bytes (on 64-bit systems), which is identical to a raw pointer! The compiler generates the delete call inline, yielding zero runtime memory or lookup overhead.
* The Deposit Box Metaphor: A unique pointer is like owning a single physical key to a deposit box. Only you hold it. You cannot duplicate the key (no copying). You can only hand the key over to someone else (moving). When you throw the key in the bin (the pointer goes out of scope), the deposit box is automatically locked and its contents are destroyed.
Custom Deleters: Managing Sockets and Files
By default, unique pointers release memory using the standard delete operator. However, you can pass a custom function or functor (called a custom deleter) to handle non memory resources, such as closing file handles, database connections, or network sockets when the pointer goes out of scope:
The Custom Deleter Size Overhead Trap
While stateless custom deleters (like our FileCloser functor or a basic stateless lambda) add zero runtime size overhead to std::unique_ptr due to compiler Empty Base Optimization (EBO) keeping it at 8 bytes, passing a raw function pointer as a deleter changes the physical structure.
If you declare std::unique_ptr<FILE, void()(FILE)>, the stack footprint of the unique pointer doubles from 8 bytes to 16 bytes because it must physically store the function pointer address alongside the object address. Always prefer stateless functors or empty lambdas to maintain the zero cost abstraction layout.