Mastering Memory Management in C++

The Power of Custom Memory Resources

When it comes to managing memory in C++, developers often find themselves torn between efficiency and complexity. Writing a custom memory resource can seem daunting, but it’s actually a straightforward process, especially with a solid understanding of memory allocators and arenas. Before diving into the world of custom memory resources, it’s essential to explore the existing implementations provided by C++.

Built-in Memory Resources

C++ offers a range of useful memory resources that can help you achieve your goals without having to write your own. All memory resources derive from the base class std::pmr::memory_resource. Some notable examples include:

  • std::pmr::monotonic_buffer_resource: Similar to the Arena class, this resource is ideal for scenarios where many objects with short lifetimes are created. Memory is only freed when the resource instance is destructed, making allocations incredibly fast.
  • std::pmr::unsynchronized_pool_resource: This resource uses memory pools (or “slabs”) containing fixed-size memory blocks, reducing fragmentation within each pool. It’s beneficial when creating many objects of a few different sizes.
  • std::pmr::synchronized_pool_resource: A thread-safe version of unsynchronized_pool_resource.

Chaining Memory Resources

Memory resources can be chained together, allowing you to specify an upstream resource when creating an instance. This upstream resource is used when the current resource cannot handle a request or needs to allocate memory itself. The <memory_resource> header provides free functions that return pointers to global resource objects, such as:

  • std::pmr::new_delete_resource(): Uses the global operator new and operator delete.
  • std::pmr::null_memory_resource(): A resource that always throws std::bad_alloc when asked to allocate memory.
  • std::pmr::get_default_resource(): Returns a globally default memory resource that can be set at runtime.

Rewriting with std::pmr::set

Let’s rewrite our previous example using a std::pmr::set:
cpp
int main() {
auto buffer = std::array<std::byte, 512>{};
auto resource = std::pmr::monotonic_buffer_resource{
buffer.data(), buffer.size(), std::pmr::new_delete_resource()};
auto unique_numbers = std::pmr::set<int>{&resource};
//...
}

Implementing a Custom Memory Resource

Implementing a custom memory resource is relatively simple. You need to publicly inherit from std::pmr::memory_resource and implement three pure virtual functions:

  • do_allocate
  • do_deallocate
  • do_is_equal

Here’s an example of a simple memory resource that prints allocations and deallocations, then forwards the request to the default memory resource:
cpp
class PrintingResource : public std::pmr::memory_resource {
public:
PrintingResource() : res_{std::pmr::get_default_resource()} {}
private:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
std::cout << "allocate: " << bytes << '\n';
return res_->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes,
std::size_t alignment) override {
std::cout << "deallocate: " << bytes << '\n';
return res_->deallocate(p, bytes, alignment);
}
//...
};

By mastering custom memory resources, you can unlock the full potential of C++ memory management and take your coding skills to the next level.

Leave a Reply

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