In our last lesson, we established a professional project structure using CMake and learned the importance of separating a class's interface from its implementation. With that foundation in place, we can now dive into one of the most powerful and defining features of C++: manual memory management. This is the bedrock of high-performance programming and is essential for building the efficient AI systems you're aiming for.
This lesson will demystify the two primary memory regions a C++ program uses: the stack and the heap. You'll learn not just their theoretical differences but also their practical implications for performance and program design. To make these concepts tangible, you will implement a class that manually allocates and deallocates memory on the heap, giving you direct, hands-on experience with the principles that underpin all complex C++ applications, including the inference engine we will build.
1. The Stack and the Heap: A Tale of Two Memories
Every variable and object in your program needs to live somewhere in memory. C++ gives you direct control over where that "somewhere" is, primarily through the choice between stack and heap allocation. These two regions serve different purposes and have vastly different performance characteristics.
- The Stack: This is a region of memory for static memory allocation. It's highly organized and efficient. When you declare a local variable inside a function (
int x = 5;), it's placed on the stack. Memory is "pushed" onto the stack when a function is called (for its local variables) and "popped" off automatically when the function returns. This makes allocation and deallocation extremely fast—often a single CPU instruction. However, the stack is limited in size (typically a few megabytes). - The Heap: This is a large pool of memory for dynamic memory allocation. It's less organized than the stack and is used for data that needs to live longer than a single function scope or is too large for the stack. You must explicitly request memory from the heap (using the
newkeyword) and explicitly release it when you're done (using thedeletekeyword). This process is much slower than stack allocation because it involves searching for a free block of memory and requires bookkeeping by the memory manager.
The following video provides an excellent visual and practical breakdown of these concepts.
Watch this video "Stack vs Heap Memory in C++" from The Cherno. It clearly explains the what, why, and how of these two memory regions.
Watch the introduction explaining the concepts of stack and heap memory. Pay close attention to the code demonstration from stack vs. heap allocation. Notice the syntax difference (int value; vs. int* value = new int;) and how the debugger shows stack variables being stored contiguously. Focus on the explanation of lifetimes and deallocation. This is the critical difference: stack memory is automatically managed by scope, while heap memory requires manual delete. Finally, watch the section on performance implications. The key takeaway is that stack allocation is an incredibly fast pointer adjustment, whereas heap allocation is a complex and comparatively slow operation involving the operating system.
When a program is running, its memory is laid out in a structured way. The stack and heap are two of the most important segments in this layout.

2. Manual Memory Management and RAII
Since heap allocation is manual, it brings a responsibility: you must manage the memory's lifetime. If you allocate memory with new and forget to release it with delete, you create a memory leak. In a long-running application like a server or an inference service, even small leaks can accumulate and eventually crash the system.
So, how do we manage this reliably? The C++ community has developed a powerful idiom called Resource Acquisition Is Initialization (RAII). The principle is simple:
- An object's constructor acquires any resources it needs (like memory from the heap, file handles, or network sockets).
- The object's destructor releases those resources.
This elegantly ties the lifetime of the resource to the lifetime of the object that manages it. When the object goes out of scope, its destructor is automatically called, guaranteeing that the resource is cleaned up.
The following article provides a superb explanation of C++'s object lifecycle management and the RAII pattern.
Understanding Memory Management, Part 2: C++ and RAII
This article explains the core C++ features that enable robust memory management.
First, read the section on Constructors and Destructors. This introduces the special member functions that C++ uses to manage an object's lifecycle. Pay attention to how new is different from malloc and delete is different from free. Next, read the section on RAII. This is the most important concept in this lesson. Understand how wrapping a raw pointer inside a class with a proper destructor prevents memory leaks. Finally, to understand why this is so important, read the sections on Shallow and Deep Copying and skip to the part about pointers. When a class contains a pointer, the default behavior is a "shallow copy," which leads to multiple objects pointing to (and trying to manage) the same memory, a recipe for disaster.
By combining heap allocation with RAII, we can build classes that safely and automatically manage dynamic resources.
3. Project: Building a DynamicArray
It's time to put theory into practice. Your task is to implement a simple dynamic array class, much like a simplified std::vector. This class will encapsulate a pointer to a heap-allocated array and manage its memory according to RAII principles. This is a foundational data structure and a perfect exercise for understanding manual memory management.
You will implement a class that:
- Starts with a certain capacity.
- Allows adding elements using a
push_backmethod. - If
push_backis called when the array is full, it will reallocate a larger block of memory on the heap, copy the old elements over, and then add the new one.
The following video is a complete walkthrough of building such a class. It's an excellent practical guide for your task.
VECTOR/DYNAMIC ARRAY - Making DATA STRUCTURES in C++
This video from The Cherno, "VECTOR/DYNAMIC ARRAY - Making DATA STRUCTURES in C++," will guide you through the implementation.
Watch the initial explanation of what a dynamic array is and how it works conceptually. Follow along with the implementation of the basic class structure, including the m_Data pointer, m_Size, and m_Capacity members, from here. The core logic is in the ReAlloc function. Watch how it's implemented from this point. Notice the three steps: allocate new memory, copy/move old elements, delete old memory. Finally, see how the PushBack function uses ReAlloc when the capacity is exceeded, from this timestamp.
Your Task
Now, you will apply these concepts to your own DynamicArray class. Continue working in the project you set up in the previous lesson, or create a new one with the same structure.
-
Define the Class Interface:
- In your
includedirectory, create a new header,DynamicArray.h. - Define a
template<typename T>class namedDynamicArray. - It should have private member variables:
T* m_Data = nullptr;size_t m_Size = 0;size_t m_Capacity = 0;
- It should have a public interface:
- A default constructor
DynamicArray(). - A destructor
~DynamicArray(). - A
push_back(const T& value)method. - A
size()method that returnsm_Size. - An
operator[]for element access.
- A default constructor
- Add a private
ReAlloc(size_t newCapacity)helper function.
- In your
-
Implement the Class:
- In your
srcdirectory, createDynamicArray.cpp. (Note: For template classes, a common practice is to put the implementation directly in the header file. For this exercise, you can start by implementing for a specific type likeintin the.cppfile, or move the implementation to the header file to make it fully generic.) - Constructor: In the constructor, you can call
ReAlloc(2)to give it an initial capacity of 2. - Destructor: The destructor should call
delete[] m_Data;. This is the RAII cleanup step! ReAlloc: Implement this function as shown in the video. It should allocate a new block withnew T[newCapacity], copy the elements,delete[]the oldm_Data, and then updatem_Dataandm_Capacity.push_back: Check ifm_Size >= m_Capacity. If so, callReAllocwith a larger capacity (e.g.,m_Capacity + m_Capacity / 2). Then, place the new value atm_Data[m_Size]and incrementm_Size.operator[]andsize(): Implement these simple accessors.
- In your
-
Test Your Implementation:
- In your
main.cpp, create an instance ofDynamicArray<int>. - Push back several integers (enough to trigger a reallocation).
- Loop through the array using
size()andoperator[]to print the elements and verify they are correct.
- In your
-
A Deeper Dive (Optional, but Recommended)
The implementation usingnew T[size]anddelete[]has a subtle but critical flaw, especially when dealing with complex objects.new T[capacity]not only allocates memory but also calls the default constructor for allcapacityelements, even those you aren't using yet. Similarly,delete[]calls the destructor on every element. This can lead to double-destruction if you also implement methods likepop_backorclearthat manually destroy elements.The correct, high-performance approach is to separate memory allocation from object construction.
VECTOR/DYNAMIC ARRAY - Making DATA STRUCTURES in C++
Watch the final, crucial part of The Cherno's video where he addresses this bug.
Focus on the section starting from "a glaring flaw". He explains why new[]/delete[] is problematic and refactors the code to use operator new (which just allocates raw memory, like malloc) and operator delete. He then manually calls destructors only when needed.
Try to refactor your `ReAlloc` and destructor to use this more robust approach. This change is a significant step toward understanding how industrial-strength containers like `std::vector` are truly built.
Conclusion
In this lesson, you've taken a significant step into the world of low-level C++ programming. You've learned the fundamental difference between stack and heap memory and, more importantly, put the RAII idiom into practice by building a class that manages its own dynamic resources.
Key takeaways:
- Stack allocation is fast and automatic but limited in size and scope.
- Heap allocation is flexible but slower and requires careful manual management using
newanddelete. - RAII is the core C++ pattern for preventing resource leaks by tying resource lifetimes to object lifetimes via constructors and destructors.
- Implementing a custom container like a dynamic array reveals the complexities of memory management, such as the need for reallocation and the subtle difference between allocating memory and constructing objects.
In our current implementation, if you tried to copy your DynamicArray (DynamicArray arr2 = arr1;), you would run into the "shallow copy" problem we read about, leading to a double-free error when both objects' destructors are called. Our class is not safe to copy.
In the next lesson, we will solve this problem by implementing the Rule of Five. You will learn how to define custom copy constructors, move constructors, and assignment operators to give your class correct and efficient copy and move semantics, a cornerstone of modern C++.