new
and delete
In the previous lesson, we introduced memory management, and how memory is allocated on the stack.
The automatic memory management done for us in the stack kept our lives simple through the beginner courses. But, we saw two big constraints of stack-allocated memory:
But, inevitably, there are going to be scenarios where we need to intervene and take control of the memory allocation and deallocation process.
When we need to do this, we will be placing our objects in dynamic memory.
For various historic reasons, areas of dynamic memory are sometimes referred to as the “free store”, or the “heap”.
In modern C++ programming, we can consider these terms as being interchangeable.
new
To create an object in dynamic memory, we use the new
 keyword:
#include <iostream>
using std::cout, std::endl;
class Character {
public:
string Name { "Frodo" };
~Character() {
cout << "Destroying Character" << endl;
}
};
int main() {
cout << new Character << endl;
}
0x624e70
On my system, this logged out 0x624e70
This looks like a memory address, which would suggest that new
returns a pointer. This is indeed the case:
int main() {
cout << (new Character)->Name << endl;
}
Frodo
Like with any other pointer, we can store this in a variable:
int main() {
Character* MyCharacter { new Character };
cout << MyCharacter->Name << endl;
}
During this program, we have memory on the stack that is being used to store a pointer. That pointer is pointing at a location in dynamic memory, that has a Character
 object.
When using new
we can still construct our objects any way we normally would. For example, we can pass arguments to the constructor:
int main() {
int* MyInt { new int { 42 } };
cout << *MyInt << endl;
Character* MyCharacter { new Character { "Gandalf" } };
cout << MyCharacter->Name << endl;
}
42
Gandalf
We can also use any expression that would create a value of the appropriate type:
Character GetGandalf() {
return Character { "Gandalf" };
}
int main() {
int* MyInt { new int { 40 + 2 } };
cout << *MyInt << endl;
Character* MyCharacter { new Character { GetGandalf() } };
cout << MyCharacter->Name << endl;
}
42
Gandalf
This allows us to address the problem from the previous chapter. We can now create an object within a function and return a pointer to that object.
Because the object is not being stored in the stack, it will remain available even when the stack frame is removed after the function ends:
#include <iostream>
using namespace std;
class Character {
public:
~Character() {
cout << "Destroying Character" << endl;
}
string Name { "Frodo" };
};
Character* SelectCharacter() {
return new Character { "Gandalf" };
}
int main() {
Character* MyCharacter { SelectCharacter() };
cout << MyCharacter->Name << endl;
}
Gandalf
Our program now logs out Gandalf
as expected. However, we never see the Destroyed Character
message from our destructor being logged.
Because we’re now allocating memory in the free store rather than the stack, we are now responsible for deleting our objects.
This no longer happens automatically and, if we do not free the memory when it is no longer needed, we have a type of defect called a memory leak.
The systems that our software runs on only have a limited amount of memory. Because of this, we need to make sure we’re freeing the memory we no longer need.
Any failure to do that creates a defect in our software called a memory leak. When we have a memory leak, our software uses more and more memory the longer it is running. When the system runs out of memory to allocate, our program is likely to run into problems.
This is a particularly nasty type of defect to have because, by its very nature, it can be difficult to detect during testing. A memory leak may take hours or days to deplete the system resources, and it can take much longer if it’s being tested on a system with a lot of memory available.
Because of this, a program that seems stable when we run it on our machine can fail in the real world.
To delete an object we previously created by calling new
, we call delete
with the pointer to the object.
int main() {
Character* MyCharacter { SelectCharacter() };
cout << MyCharacter->Name << endl;
delete MyCharacter;
}
Our program now works as expected, and cleans up after itself:
Gandalf
Destroying Character
malloc
, and free
new
and delete
are the “C++ way” of allocating dynamic memory. In C, the equivalent syntax is malloc
and free
.
malloc
and free
are still available to us when writing C++ code but, in general, they should be avoided.
The C equivalents are not type-safe, and they do not call the constructor for the object we are creating in memory.
Additionally, in some compilers, the way memory is allocated can be different. As such, mixing and matching the C allocators with the C++ allocators can cause problems.
new
, it should be deallocated with delete
.malloc
, it should be deallocated with free
.new
and delete
In complex programs, managing memory manually using new
and delete
can become very difficult. This is particularly true when many different components of our system are relying on the same objects stored in the free store.
Additionally, even code that looks safe can have a memory leak:
void MyFunction() {
int* ptr { new int { 42 } };
AnotherFunction();
delete ptr;
}
In this seemingly safe example, it is possible for AnotherFunction
to fail, but for our program to recover. We talk about how to recover from failures in a future chapter on Exception Handling but, even applying those techniques, we would still be left with a memory leak. This is because MyFunction
would fail before line 4 is executed.
In complex software, the responsibility to delete every object, exactly once, at the correct time, and in every scenario, is too much of a burden.
Because of this, we adopt a more structured design around how memory is managed, and we use smart pointers to help us implement it. We’ll cover this in the next lesson.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.