Special Allocators with C++17
I introduced in my last post “Polymorphic Allocators with C++17” the theory of polymorphic allocators in C++17. Today, I will apply the theory.
Before I go on, here are the essential parts of my last post: “Polymorphic Allocators with C++17“.
A Short Reminder
The following program uses polymorphic allocators.
// polymorphicAllocator.cpp #include <array> #include <cstddef> #include <memory_resource> #include <vector> int main() { std::array<std::byte, 200> buf1; // (1) std::pmr::monotonic_buffer_resource pool1{buf1.data(), buf1.size()}; std::pmr::vector<int> myVec1{&pool1}; // (3) for (int i = 0; i < 5; ++i) { myVec1.push_back(i); } char buf2[200] = {}; // (2) std::pmr::monotonic_buffer_resource pool2{std::data(buf2), std::size(buf2)}; std::pmr::vector<int> myVec2{&pool2}; for (int i = 0; i < 200; ++i) { myVec2.push_back(i); } }
Now, I want to focus on myVec2
. It pushes 200 int
s onto the std::pmr::vector<int>
. 200 int
s do not fit into a char buf[200]
and, therefore, std::pmr::new_delete_resource()
as the so-called upstream allocator kicks in and calls the global new
for the remaining elements. Let me instrumentalize the upstream allocator.
A Tracking Allocator
The following program is based on the previous one, uses a tracking allocator, and makes the dynamic memory allocation and deallocation visible.
// trackAllocator.cpp #include <array> #include <cstdlib> #include <format> #include <iostream> #include <memory_resource> #include <vector> class TrackAllocator : public std::pmr::memory_resource { void* do_allocate(std::size_t bytes, std::size_t alignment) override { void* p = std::pmr::new_delete_resource()->allocate(bytes, alignment); std::cout << std::format(" do_allocate: {:6} bytes at {}\n", bytes, p); return p; } void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override { std::cout << std::format(" do_deallocate: {:4} bytes at {}\n", bytes, p); return std::pmr::new_delete_resource()->deallocate(p, bytes, alignment); } bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { return std::pmr::new_delete_resource()->is_equal(other); } }; int main() { std::cout << '\n'; TrackAllocator trackAllocator; // (1) std::pmr::set_default_resource(&trackAllocator); // (2) std::cout << "myVec1\n"; std::array<std::byte, 200> buf1; std::pmr::monotonic_buffer_resource pool1{buf1.data(), buf1.size()}; std::pmr::vector<int> myVec1{&pool1}; // (3) for (int i = 0; i < 5; ++i) { myVec1.push_back(i); } std::cout << "myVec2\n"; char buf2[200] = {}; std::pmr::monotonic_buffer_resource pool2{std::data(buf2), std::size(buf2)}; std::pmr::vector<int> myVec2{&pool2}; // (4) for (int i = 0; i < 200; ++i) { myVec2.push_back(i); } std::cout << '\n'; }
TrackAllocator
is a tracking allocator. It derives from the interface class std::pmr::memory_resource
, from which all memory resources derive. TrackAllocator
defines the three required member functions do_allocate
, do_deallocate
, and do_is_equal
. Each call forwards its call to std::pmr::new_delete_resource
. std:pmr::new_delete_resource
is the default memory resource and calls the global new
and delete
. The member function’s job do_is_equal
is to
check that the two memory resources are equal. The interesting point in the program is the visualization of the allocation and the deallocation in the member functions do_allocate
and do_deallocate
.
By instantiating the TrackAllocator
(line 1) and make it as the default resource (line 2). myVec1
(line 3) and myVec2
(line 4) use them as the upstream allocator. This allocator kicks in if the primary allocator is consumed. This fallback is not necessary for myVec1
, but necessary for myVec2
.
This output shows the dynamical allocation and deallocation of myVec2
until all int
s fit into the std::pmr::vector<int>
.
You can also bind an upstream allocator to a specific container.
A Non-Allocating Allocator
std::pmr::null_resource_allocator
is a special allocator. Using this memory resource for allocating causes a std::bad_alloc
exception. This memory resource ensures that you do not arbitrarily allocate memory on the heap. Let’s try it out.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
// nullMemoryResource.cpp #include <array> #include <cstddef> #include <iostream> #include <memory_resource> #include <string> #include <vector> int main() { std::cout << '\n'; std::array<std::byte, 2000> buf; std::pmr::monotonic_buffer_resource pool{buf.data(), buf.size(), // (1) std::pmr::null_memory_resource()}; std::pmr::vector<std::pmr::string> myVec{&pool}; // (2) try { for (int i = 0; i < 100; ++i) { // (3) std::cerr << i << " "; myVec.emplace_back("A short string"); } } catch (const std::bad_alloc& e) { // (4) std::cerr << '\n' << e.what() << '\n'; } std::cout << '\n'; }
First, I allocate memory on the stack and initialize std::pmr::monotonic_buffer_resource
with it. Then, I use this memory resource and std::pmr::null_memory_resource
as an upstream allocator (line 1). In line 2, I create a std::pmr::vector<std::pmr::string>
. Because I use a std::pmr::string,
the string also uses the memory resource and its upstream allocator. When I use std::pmr::vector<std::string>
, std::string
allocates using the global new
and delete
. Finally, in line (3), I create 100 strings and catch in line (4) a std::bad_alloc
exception. I got what I deserved. 100 strings will not fit into a std::array<std::byte, 2000>
buffer. After 17 strings, the buffer is consumed.
What’s Next?
You may have heard it already. The std::pmr::monotonic_buffer
has outstanding features. It is pretty fast and does not free memory. I will provide the number in my next post.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,