Skip to main content

A smart memory block

The allocator API is built around the abstraction of a memory block. Unlike traditional designs that store only a pointer to the allocated data, this approach encapsulates additional metadata: the size of the resource and a reference to its allocator. The structure is defined as follows:

In this design, alloc is an opaque object that conforms to the __Allocator interface. This interface includes a handle to the concrete allocator and a vtable with a single function pointer, __allocators_best_friend, which manages allocation, reallocation, and deallocation in a unified manner. The alloc field occupies 16 bytes—8 bytes for the allocator handle and 8 bytes for the function pointer—providing a compact yet versatile mechanism for memory management.

This simple yet flexible design offers significant advantages. For instance, a block can free it's resources when it runs out of scope or reallocate new memory when the current resource is too small. The following sections will explore the key benefits of this approach in detail.

Ownership

The allocator API distinguishes between three states of a memory block: empty blocks, blocks that borrow a resource, and blocks that own a resource. These states are determined by the presence or absence of a pointer to the resource (alloc.ptr) and a handle to the allocator (alloc.handle), as defined by the following methods:

This design enables straightforward ownership verification by leveraging access to the concrete allocator instance. For a given allocator A, the ownership check is implemented as:

Here, the method compares the allocator’s handle (self) with the block’s allocator handle, cast to an opaque pointer type, ensuring a match only for non-empty blocks managed by A.

This is powerful, because an owns method like this makes it possible to construct allocators that are compositions of others (see the talk of Andrei Alexandrescu on composable allocators in C++).

The ability to query ownership in this manner unlocks significant flexibility. It facilitates the construction of composite allocators—allocators built from combinations of other allocators—similar to the composable allocator designs discussed by Andrei Alexandrescu in his talk on C++ allocator design). This capability enhances modularity and reuse, allowing developers to tailor memory management strategies to complex application requirements.

Deallocation

By implementing __dtor from the new RAII pull request, the handle to the concrete allocator instance allows the block to be freed automatically when it runs out of scope:

Similarly, it can allocate (when block is empty) or reallocate itself with the same allocator when requested. I'll get back to the implementation of the method __allocators_best_friend shortly.

Copy construction / assignment

A specialized copy assignment is implemented (from the new RAII pull request) that returns a non-owning view of the data

This means that a resource is only owned by a single object, resulting in safe resource management (no double free's, etc).

Opaque blocks versus typed blocks and

The standard block, used by allocators, is an opaque block (T = opaque). A __cast metamethod is implemented that can cast block(opaque) to any block(T), thereby reinterpreting the memory. Such a typed block can be used in containers.

Notion of size

The size of the allocated resource in terms of bytes is explicitly stored in the struct definition. The current size of a typed block in terms of number of elements T is then computed as