Looking at heap allocation
published at 10.10.2026 15:46 by Jens Weller
Save to Instapaper Pocket
Part three in the new C++ content series, visiting the heap. Meeting C++ 2026 is only 7 weeks away!
Maybe read last weeks post exploring the stack first.
Stack memory does have one big disadvantage: its on the stack - which only accounts for things during compilation. While this makes your variable already placed/loaded into memory when the code block of a function is executed, its also going to be destroyed once the function returns or a block of code ends. The only way around this are parameters to write to, returning a single value or using a heap allocation.
Heap allocations provide allocations needed during runtime, as the stack can not account for this. These runtime allocations then get assigned to pointers or pointer-wrapping structures residing on the stack usually. These memory allocations are not freed automatically, its up to the runtime to take care of freeing these allocations. Smart pointers residing on the stack are the go to way to ensure memory is freed correctly in C++. This approach is also known as RAII, where the allocation or resource is freed once the object on the stack gets destroyed.
In Modern C++ these allocations are usually managed with unique_ptr or shared_ptr. An exception to this rule are frameworks that historically choose to manage their allocations, like Qt managing allocations via assignment to a parent derived from QObject. Also all containers, which manage the memory via allocators use *mostly* dynamically allocated memory. By default this is done on the heap, with pmr containers/allocators there is also an option to have these operate on supplied memory, including arrays on the stack via the standard. And std::string is allocating a buffer for its content on the heap, but for small strings there is an optimization utilizing a union of the members of the type and an char array. A string that is small enough can fit into the memory of the std::string instance it self, instead of causing a heap allocation.
Lets unwrap the last paragraph into each of its ways to enable runtime allocations on the heap.
With C++11 std::unique_ptr and shared_ptr were introduced, and auto_ptr was removed from the language. Both unique_ptr and shared_ptr by default allocate via new/delete with their make_unique / make_shared creation functions, but can be customized to call a deletion function to free the memory or resource. This can be used to manage resources that are not allocations coming from new or to add additional functionality like logging.
C++23 brought two other types for managing memory with unique_ptr or shared_ptr, when interfacing with C functions taking in arguments as T**/void**: out_ptr and inout_ptr. While out_ptr is for functions that only allocate to their pointer** parameters, inout_ptr is for functions that also reset the ownership of their pointer** argument. Both will reset the smart pointer given to them, but inout_ptr does this via release, leaving the actual deallocation to the called function. For shared_ptr exists the specific requirement to also pass in its deleter.
An example for out_ptr:
auto uptr = make_unique_ptr(42); query_number(std::out_ptr(uptr)); //query number takes an int**
query_number writes to uptr, changing its value from 42 to 24. The example for out_ptr at cppreference uses sqlite3_create to manage access to a sqlite db. More details about this you’ll find in a blog post by Sandor Dargo.
One should prefer unique_ptr over shared_ptr, as most allocations do not have the requirement to be shared. As a unique_ptr is only moveable, you can not copy it, this is a feature – preventing unnecessary copies. You still can share a unique_ptr as a reference with various functions, also a raw pointer to the allocated object is often used for this via get(). You can transfer the ownership of a unique_ptr via its release method, a shared_ptr does not allow for this.
C++26 goes a step further, and offers value based wrappers with std::polymorphic a type for handling polymorphism with virtual member functions and std::indirect which provides a thin wrapper for an allocation in situations like implementing a pimpl pattern. The value based approach makes these types behave like a normal type. Unlike unique_ptr these types can not be empty, they need to be initialized with an instance, the member function valueless_after_move allows for checking if the allocation has been moved out of the variable. The allocation is based the given allocator, pmr allocators are supported. Arrays and non-object types are not supported. Both types provide an smart-pointer like interface with overwritten operator ->/*. You can read about an implementation of std::polymorphic on Antoine Morriers blog.
But back to unique_ptr. Until C++26 it is the default one should go for when handling an allocation. Though one should first wonder about if the allocation can be avoided or handled better in a container or through other alternatives. Type t; is often as good as unique_ptr pt; Polymorphism can also be handled through references: Base& b = t; b.myvirtual(); is valid code. But often you can not avoid the allocation, and then unique_ptr offers a clear ownership for your design. This is also helpful when managing resources and allocations coming from 3rd party libs, where the ownership is transferred to you.
And when should you bring shared_ptr into consideration? From an ease of use it can be a false friend to C++ programmers. A copy of a unique_ptr is a compilation error, when you use a moved from unique_ptr its a runtime error. It seems shared_ptr has none of these issues. With its reference counted semantics it seems the perfect smart pointer. With make_shared you even are able to turn the allocations for the counting variables and the type instance into one allocation. C++20 even brought another feature: allocate_shared allows you to use an allocator to create a shared_ptr! Even a shared_ptr holding memory on the stack with a pmr::allocator is now possible! Ok, I got to admit, that last one is kinda good. It allows you to stick all the allocations for your shared_ptrs in one place, instead of having them potentially be fragmented in memory. Unfortunately this is for unique_ptr not yet proposed, but could be implemented with the custom deleter. Here is a very light implementation just using an std::allocator:
template < class T, class Alloc = std::allocator>
struct alloc_deleter
{
alloc_deleter(Alloc& alloc):alloc(alloc){}
void operator()(T* ptr)
{
alloc.deallocate(ptr,1);
}
private:
Alloc& alloc;
};
template < class T, class Alloc = std::allocator
std::unique_ptr<T,alloc_deleter> make_unique_alloc(Alloc& alloc)
{
return std::unique_ptr<T,alloc_deleter >(alloc.allocate(1),alloc_deleter{alloc});
}
int main()
{
std::allocator< int > alloc;
auto uptr = make_unique_alloc(alloc);
*uptr = 5;
std::println("value: {}",*uptr.get());
return 0;
}
Link to Compiler Explorer
This is very raw, allocators return uninitialized memory, so a better implementation would take care of returning unique_ptr with initialized memory with a provided value.
Maybe lets first talk about the general downsides of allocations, which we use smart pointers for. The fragmentation of your memory/allocations can be a disadvantage for your code at runtime, it will cause cache misses. A vector is faster then a vector<unique_ptr>. An allocator can help you to have all these placed in memory into the same allocation or similar sized chunks. When accessed as a collection through a container you still have one more indirection as when you have the type in that vector directly in memory.
For a particular downside of shared_ptr let me tell you a story which started in 2012. In May 2012 I’ve visited C++now, C++11 was just released and there was lots of buzz about modern C++, the new standard and boost. After all the conference had just been renamed from boostcon to C++now. As a freelancer in C++ I was familiar with scoped_ptr and shared_ptr from boost, and now they made it to the standard! One of the talks I’ve attended was by Sean Parent - Value Semantics and Concepts-based Polymorphism (https://www.youtube.com/watch?v=_BpMYeUFXv8), a mind blowing talk for the time for many. Sean had a particular slide about shared_ptr, showing the concept of sharing an allocated instance T with two shared_ptrs. The shared_ptrs were two rectangles in the top of the slide, pointing to a T in another rectangle on the center bottom of the slide. Around the boxes and arrows was a single red and rounded outline drawn in the vague shape of a heart. I was excited to see that Sean Parent loves shared_ptr as much as I did back than!
At one point it dawned on me that this might not have been Seans intention. As I’ve learned from the online discourse and talking with folks at conferences – not everybody loved shared_ptr. When I’ve had the chance to talk to him, we had a conversation on this topic. He laughed at my initial misunderstanding the slide, and then replied something like “No, it destroys your local reasoning”.
Let me explain.
When you look at a block of code you should be able to reason about it locally in Seans opinion. In a multi threaded and asynchronous world like today that is important. You don’t want to worry about some spooky action in a distance, all you see is your block of code. And Sean pointed out, that a shared_ptr can break this, as you are now in a shared context. A shared_ptr is a global variable, made for sharing. There are cases where this is what you want. But most often it is not. The shared_ptr forces you to understand the other code blocks where it is used, in order to reason about the one you are looking at. So when using shared_ptr, have in mind that it is a global variable in disguise.
A shared_ptr can be a good way to find a compromise, Sean used this as an implementation detail even in his talk in 2012. And sometimes that shared context is exactly what you want, boost::asio uses shared_ptr with shared_from_this to create a context that stays valid in a chain of asynchronous calls to read or write from the network. For these kind of callbacks shared_ptr is often a great way to manage and keep the context alive between the chain of callbacks. Though coroutines have also emerged as an alternative way to implement such code, with C++20 this might become the new way to implement such patterns in the future. Though for the moment coroutines still have a large implementation overhead. David Maziéres shared his experience in implementing such a use case in this blog post. For the call back based approach often the base class enable_shared_from_this is used, as its handy if any instance of a class shall be a shared_ptr.
With shared_ptr comes also weak_ptr, its a class that holds only the information to the reference counting, but not the allocated instance it self. This enables weak_ptr to know if the allocated object still exists, and in that case you can ask it to return a shared_ptr to you. This acts as a means to break cyclic references, which otherwise would cause a memory leak. Also it allows for having the shared context minimized to the code blocks where you actually need to access the shared_ptr.
In my own code for a tree I use a TreeItem class that is derived from enable_shared_from_this and has a weak_ptr member for its parent:
template< class Type>
class TreeItem : public std::enable_shared_from_this< TreeItem< Type> >
{
using item_t = std::shared_ptr< TreeItem< Type > >;
using self = TreeItem< Type>;
using const_item_t = std::shared_ptr< const TreeItem;
using weak_item_t = std::weak_ptr< TreeItem< Type > >;
std::vector< item_t > children;
weak_item_t parent;
…
};
For an external API this class needs to be able to give the position of a tree node in its parents child vector:
int row()const
{
if(parent.expired())
return 0;
return parent.lock()->childPos( self::shared_from_this());
}
In weak_ptr you can use lock() to get a shared_ptr instance to access the object held by the shared_ptr. With expired() one can test if there still is a valid instance to be accessed.
I’m not sure how I would implement this today, if I’d still pick shared_ptr or if unique_ptr is an alternative. But 10 years ago I did opt for shared_ptr when implementing my own CMS for Meeting C++.
There is no enable_unique_from_this class to derive from. As unique_ptr is meant to be small and as efficient as possible, this also means that there is no room for mechanisms like this one. One could try to emulate a base class which would have to rely on a factory function to do the magic trick of holding the unique_ptr, while returning a reference of it by its create function. CRTP (the Curiously Recurring Template Pattern) comes to mind to implement this. But this will run into issues, as you’d need to create the unique_ptr first, and then move it into the container held by the parent class. Its very easy to step into undefined behavior here too.
One other thing that a shared_ptr has, is an aliasing constructor: shared_ptr(shared_ptr&& r, element_type* ptr). With this a shared_ptr can be constructed that holds a pointer to a different type, but is part of the ownership block of r. The pointer given to the constructor is hence not freed when this shared_ptr goes out of scope. This is useful when you have a shared_ptr holding a base class, but would like to create a shared_ptr with the actual down casted pointer of the actual class. Similar this can be used to share a pointer that is tied to the allocation of the ownership of the shared_ptr r. The created shared_ptr does not manage the life time of the pointer it stores, hence either this is done by the object owned in the ownership of r or taken care of by other means. You can also use this constructor with a nullptr as the last argument in order to extend the lifetime of the passed in shared_ptr.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!