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 ofunsynchronized_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 throwsstd::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.