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 ints onto the std::pmr::vector<int>. 200 ints 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 ints 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.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Embedded Programming with Modern C++": January 2025
  • "Generic Programming (Templates) with C++": February 2025
  • "Clean Code: Best Practices for Modern C++": May 2025
  • 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)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,