Dynamic memory management in C++
The allocation and deallocation of memory during a program’s runtime, as opposed to compile time, is known as dynamic memory management in C++. When the precise amount of RAM needed is uncertain beforehand, this is especially helpful.
Where Memory is Allocated: The Free Store (Heap)
The free store or heap is a memory pool used by C++ programs for dynamically allocated objects. Objects on the heap have a dynamic lifetime rather than static memory, which is used for global, static local objects and class static data members that exist for the duration of the program, or memory on the stack, which is used for local, non-static objects that are automatically created and destroyed when their block exits. When these objects are no longer required, the computer code must explicitly destroy them.
Dynamic Memory Allocation Operators: and
New and delete are C++’s main dynamic memory management operations. These are preferred over C-style functions like free() and malloc() due to their automated type handling and overloading.
new operator
- Memory from the free store is allocated.
- Provides the freshly allocated object’s pointer back. Objects allocated dynamically lack names.
- For custom types, each object created by new immediately invokes the default constructor. The object is typically initialised to zero for built-in types.
- It Has the ability to allocate memory for both single and arrayed items.
- Syntax for individual object: new type; pointer-variable = new;.
- Syntax for array of objects: pointer-variable = new type[size];. Initialisation of arrays must take place after the buffer is established; it cannot be done directly in the new statement.
- Error Handling: By default, std::bad_alloc is thrown if new is unable to allocate the given storage. To avoid this, use new (nothrow) type;, which instead returns nullptr on failure.
delete operator
- Releases memory that was given to new.
- Performs two actions: It calls the destructor to destroy the object that its provided pointer points to, and then it releases the associated memory thereafter.
- Syntax for individual object: Eliminate the pointer-variable;.
- Syntax for array of objects: edit[] pointer-variable;. The [] ensures that the destructor is invoked for each element in the array by alerting delete that the entire array is being released.
- You can delete without any impact by passing a NULL or nullptr.
- Using the correct version of delete (with or without []) that matches the new used for allocation is essential. Invalid pointer usage with delete is an undefined behaviour that can lead to major issues.
Code Example: Basic Dynamic Memory Allocation
#include <iostream> // For input/output operations
#include <string> // For std::string
int main() {
// 1. Allocate memory for a single built-in object (int)
// The 'new' operator allocates memory and returns a pointer to it.
int* pValue = new int; // Request memory for an int
// Store a value at the allocated address
*pValue = 123;
std::cout << "Value of pValue: " << *pValue << std::endl;
// Release memory pointed to by pValue.
// 'delete' first calls destructor (if any) then frees memory.
delete pValue;
pValue = nullptr; // Good practice to nullify the pointer after deletion to prevent dangling pointers
std::cout << "---------------------------------" << std::endl;
// 2. Allocate memory for an array of built-in objects (int)
int arraySize;
std::cout << "Enter array size: ";
std::cin >> arraySize;
// 'new int[arraySize]' allocates an array of 'arraySize' integers.
// It returns a pointer to the first element.
int* pArray = new int[arraySize];
// Initialize the array elements
for (int i = 0; i < arraySize; ++i) {
pArray[i] = (i + 1) * 10;
}
// Output array elements
std::cout << "Array elements: ";
for (int i = 0; i < arraySize; ++i) {
std::cout << pArray[i] << " ";
}
std::cout << std::endl;
// Release memory for the entire array.
// 'delete[]' is essential for arrays to ensure proper cleanup of all elements.
delete[] pArray;
pArray = nullptr; // Nullify the pointer
std::cout << "---------------------------------" << std::endl;
// 3. Allocate memory for an object of a user-defined class (std::string)
// The constructor of std::string will be automatically called.
std::string* pString = new std::string("Hello Dynamic Memory!");
std::cout << "Value of pString: " << *pString << std::endl;
// The destructor of std::string will be automatically called, then memory freed.
delete pString;
pString = nullptr;
return 0;
}
Output
Value of pValue: 123
---------------------------------
Enter array size: 3
Array elements: 10 20 30
---------------------------------
Value of pString: Hello Dynamic Memory!
Challenges with Raw Pointers
Raw new and delete operators are infamously difficult and a major source of errors when it comes to managing dynamic memory. Common issues consist of:
Memory Leaks: Memory that has been dynamically allocated is never returned to the free storage if it is not deleted. Performance may suffer over time as a result of the RAM being used up.
Dangling Pointers: occurs when a pointer uses memory that has been released but may still contain outdated or erroneous information or be used by other program components. There is unknown behaviour when a dangling pointer is used.
Double Deletion: Removing the same memory twice can cause crashes and damage the memory management system.
Incorrect delete version: For an array, using delete rather than delete[] or vice versa can result in memory corruption and improper destructor calls.
Manual memory management using new and delete is verbose, laborious, and prone to errors because of these issues.
You can also read C++ Polymorphism: Core Object Oriented Programming Concept
Modern Approach: Smart Pointers (RAII)
Smart pointer types are provided by the C++ Standard Library to automatically handle dynamically allocated objects in order to mitigate the risks and complications associated with raw pointers. Smart pointers use the Resource Acquisition Is Initialization (RAII) paradigm, which requires constructors to acquire and destruction to free memory. Even with exceptions, resources are properly released when their owner exits scope.
The header provides access to smart pointer types defined by the library:
std::shared_ptr: lets several pointers reference the same item. Due to reference count management, the object is only deleted after its final shared_ptr exits scope. Building shared_ptrs with Std::make_shared is safer.
std::unique_ptr: Since unique_ptr “owns” the object it points to, only one can point to it. Destroying unique_ptr destroys the object. The use of unique_ptrs for exclusive ownership is common.
std::weak_ptr: A “weak” reference to an object under shared_ptr management is provided. It avoids circular dependencies that could cause memory leaks with shared_ptrs because it doesn’t add to the reference count.
Code Example: Dynamic Memory Allocation with Smart Pointers
#include <iostream>
#include <memory> // For std::shared_ptr, std::make_shared
#include <string>
int main() {
// 1. Using std::shared_ptr for a single object
// std::make_shared safely allocates and initializes an object in dynamic memory
// and returns a shared_ptr to it.
// No explicit 'delete' is needed; the memory is automatically freed.
std::shared_ptr<int> sharedIntPtr = std::make_shared<int>(42);
std::cout << "Value of sharedIntPtr: " << *sharedIntPtr << std::endl;
// When sharedIntPtr goes out of scope, the memory it points to will be automatically freed.
std::cout << "---------------------------------" << std::endl;
// 2. Using std::shared_ptr for a dynamic array (requires a custom deleter)
// shared_ptrs do not directly support dynamic arrays, so a deleter (e.g., a lambda)
// is needed to call delete[].
std::shared_ptr<double> sharedArrayPtr(new double[156], [](double* p) {
delete[] p; // Custom deleter to use delete[]
});
// Initialize array elements
for (int i = 0; i < 5; ++i) {
sharedArrayPtr.get()[i] = (double)(i + 1) * 2.5; // Use .get() to access the raw pointer
}
std::cout << "Dynamic array elements (shared_ptr): ";
for (int i = 0; i < 5; ++i) {
std::cout << sharedArrayPtr.get()[i] << " ";
}
std::cout << std::endl;
// Memory will be freed when sharedArrayPtr goes out of scope.
std::cout << "---------------------------------" << std::endl;
// 3. Using std::unique_ptr for exclusive ownership
// No explicit 'delete' needed.
std::unique_ptr<std::string> uniqueStringPtr(new std::string("Exclusive ownership!"));
std::cout << "Value of uniqueStringPtr: " << *uniqueStringPtr << std::endl;
// Transfer ownership (unique_ptr cannot be copied)
std::unique_ptr<std::string> anotherUniqueStringPtr = std::move(uniqueStringPtr);
if (uniqueStringPtr == nullptr) {
std::cout << "Original uniqueStringPtr is now null after move." << std::endl;
}
std::cout << "Value of anotherUniqueStringPtr: " << *anotherUniqueStringPtr << std::endl;
// Memory will be freed when anotherUniqueStringPtr goes out of scope.
return 0;
}
Output
Value of sharedIntPtr: 42
---------------------------------
Dynamic array elements (shared_ptr): 2.5 5 7.5 10 12.5
---------------------------------
Value of uniqueStringPtr: Exclusive ownership!
Original uniqueStringPtr is now null after move.
Value of anotherUniqueStringPtr: Exclusive ownership!
The Class
A mechanism to keep memory allocation and object building apart is offered by the std::allocator class. When it comes to performance or specific memory management, the allocator provides more precise control than the new method, which combines these two processes. Raw memory can be allocated and objects can be constructed in it by an allocator object. The destroy method calls object destructors without releasing memory, and the deallocate method releases the raw memory. The internal allocators of std::vector and std::string containers manage storage.
C++ dynamically allocates and dealslocates heap memory. Raw pointers with new and delete can leak memory and dangle pointers despite this control. In modern C++, RAII and smart pointers (shared_ptr, unique_ptr, and weak_ptr) automate memory management and improve code reliability and safety.
You can also read Constructors and Destructors In Inheritance C++ Explained