This module explores how C++'s use of pointers and memory allocation differs from C.
While pointers are used in much the same way in both languages, C++ introduces some interesting new features. In addition, C++ allows you to control the allocation and deallocation of a system-provided memory pool. This feature is particularly important for using dynamic data structures such as lists and trees.
As a C++ systems programmer, you have several language features that allow you to control the allocation and deallocation of memory, including system-provided
memory pools[1]. These features provide fine-grained control over memory management, a critical aspect of system programming.
Key C++ Features for Memory Pool Management:
-
Custom
new
and delete
Operators:
-
Placement
new
:
-
Memory Resource Classes (
<memory_resource>
in C++17):
- C++17 introduced the
<memory_resource>
library, which provides std::pmr::memory_resource
as an abstraction for custom memory pools. It allows you to create and manage memory pools flexibly.
std::pmr::monotonic_buffer_resource pool{1024}; // 1KB buffer
std::pmr::vector<int> vec(&pool); // Uses the memory pool
-
Allocator Framework:
- C++’s Standard Template Library (STL) supports custom allocators for containers, allowing you to direct allocations to a specific memory pool.
template <typename T>
class CustomAllocator {
public:
T* allocate(size_t n) {
return static_cast<T*>(MyMemoryPool::allocate(n * sizeof(T)));
}
void deallocate(T* ptr, size_t) {
MyMemoryPool::deallocate(ptr);
}
};
std::vector<int, CustomAllocator<int>> vec;
-
Low-Level Memory Functions (
malloc
, free
, mmap
):
- You can directly use system-level APIs like
malloc
, free
, or mmap
to allocate large blocks of memory and subdivide them into smaller pools.
void* pool = std::malloc(1024); // Allocate 1KB pool
// Use the pool for custom allocations
std::free(pool); // Free the entire pool
-
Smart Pointers with Custom Deleters:
- Use smart pointers like
std::unique_ptr
or std::shared_ptr
with custom deleters to manage memory allocated from a memory pool.
std::unique_ptr<int, void(*)(void*)> ptr(
static_cast<int*>(MyMemoryPool::allocate(sizeof(int))),
[](void* p) { MyMemoryPool::deallocate(p); }
);
-
RAII (Resource Acquisition Is Initialization):
- C++’s RAII idiom ensures that allocated resources are automatically released when objects go out of scope. This is often used with custom classes managing memory pools.
class MemoryPool {
public:
MemoryPool(size_t size) { /* Allocate pool */ }
~MemoryPool() { /* Deallocate pool */ }
};
-
Scoped Allocators (
std::scoped_allocator_adaptor
):
- In cases of nested containers (e.g.,
std::vector<std::string>
), std::scoped_allocator_adaptor
ensures all allocations come from the same memory pool.
using PoolAlloc = std::scoped_allocator_adaptor<CustomAllocator<int>>;
std::vector<std::vector<int, PoolAlloc>, PoolAlloc> nested_vec(pool_alloc);
-
Explicit Memory Control via System APIs:
- In some cases, system-level APIs (e.g.,
VirtualAlloc
on Windows, mmap
on Unix) allow you to reserve and manage memory manually for the memory pool.
-
Object Pools and Custom Allocators:
- You can implement your own object pool to reuse allocated objects efficiently, avoiding frequent allocations and deallocations.
class ObjectPool {
std::vector<MyClass*> pool;
public:
MyClass* allocate() { /* Reuse or create new */ }
void deallocate(MyClass* obj) { /* Return to pool */ }
};
Summary:
These features give you precise control over memory usage, enabling efficient management of memory pools critical for performance-sensitive or resource-constrained applications. Among these, `new`/`delete` overloading, `std::pmr` resource management, and custom allocators are especially relevant for creating and managing system-provided memory pools in modern C++.
C provides a remarkably useful type of variable called a
pointer. A pointer is a variable that stores an address and its value is the address of another location in memory that can contain a value. You already used an address when you used the
- scanf() and
- scanf_s()
functions. A
pointer variable with the name pNumber is defined by the second of the following two statements:
int Number = 25;
int *pNumber = &Number;
You declare a variable, Number, with the value 25, and a pointer, pNumber, which contains the address of Number. You can now use the variable pNumber in the expression *pNumber to obtain the value contained in Number.
The * is the
dereference operator, and its effect is to access the data stored at the address specified by a pointer.
This module discusses:
- How to create
const
pointer arguments to functions
- How to create aliases for variables using reference declarations
- How C++ implements call-by-reference using reference declaration
- How to use a generic pointer type
- How to use
new
and delete
to manipulate free store memory
- How to create dynamically allocated multidimensional arrays
At the end of the module, you will be given the opportunity to take a quiz covering these topics.
[1]system-provided memory pool: In C++, a system-provided memory pool is a specialized mechanism offered by the underlying operating system or runtime environment to allocate and manage memory more efficiently than the standard malloc/free functions. While C++ itself doesn't have a built-in memory pool implementation, some operating systems and real-time environments often provide them as part of their standard library or specific system calls.