Memory Management: Deallocation and Destructors
The Delete Keyword
As already mentioned, an important difference between Java and C++ is the concept of releasing an object and its destruction.
In Java, the Garbage Collector is responsible for releasing memory. This means that programmers may not explicitly deallocate objects. Programmers may define a finalize() method for each class, which will be called by the Garbage Collector upon releasing the object.
In C++, the programmer must explicitly call the delete operator. Upon calling delete, the destructor of the object class will be called.
The delete keyword must be used whenever the new keyword is used and the object is created on the heap because the memory management is left to the programmer’s care. When an object is allocated as an array with new int[2] for instance, then the programmer must deallocate the object with the delete [] statement.
class Dummy {
public:
Dummy() { }
};
int main() {
// Delete an object
Dummy* d = new Dummy();
delete d;
d = nullptr;
// Delete an array
constexpr int N = 2;
Dummy* dummies = new Dummy[N];
for (int i = 0; i < N; i++) {
dummies[i] = Dummy();
}
delete[] dummies;
dummies = nullptr;
return 0;
}
The Destructor of a Class
Upon calling delete, the destructor of the object class will be called. The concept of a class destructor is illustrated in the updated Point class. In this example, the destructor doesn’t have meaningful statements, but a destructor can contain instructions like the constructor does.
#include <iostream>
class Point {
public:
// constructors
Point() : _x(0), _y(0) {
std::cout << "Point default constructor called" << std::endl;
}
Point(float x, float y) : _x(x), _y(y) {
std::cout << "Point constructor called" << std::endl;
}
// destructor
~Point() {
std::cout << "Point destructor called" << std::endl;
}
// public methods
void move(float dx, float dy) {
_x += dx;
_y += dy;
}
float x() { return _x; }
float y() { return _y; }
private:
// private data fields
float _x;
float _y;
};
int main() {
std::cout << "Begin of the program" << std::endl;
Point p1(1, 2);
Point* p2 = new Point(2, 3);
delete p2;
p2 = nullptr;
std::cout << "End of the program" << std::endl;
return 0;
}
Key Points About Destructors
- Unlike a constructor, a destructor does not take any argument. So a destructor cannot be overloaded. There may be several constructors for a class, but there may be only a single destructor.
- Like a constructor, a destructor does not return any value.
When Destructors Are Called
The destructor is called whenever an object’s lifetime ends, which includes in particular:
- The use of the
deleteexpression, for objects with dynamic storage duration. - End of scope, for objects allocated on the stack.
Smart Pointers
C++ provides smart pointers as a safer alternative to raw pointers for automatic memory management:
std::unique_ptr: Owns exclusive memorystd::shared_ptr: Allows multiple owners of the same memory
These automatically deallocate memory when they go out of scope, reducing the risk of memory leaks.
#include <memory>
void function()
{
// solution with raw pointer
char* pArray = new char[100];
// do something with pArray
// say that we have a
bool condition = false;
if (condition) {
// at this point, a memory leak arises since pArray is not released
return;
}
// call delete []
delete [] pArray;
pArray = nullptr;
// solution with unique_ptr
// memory will be automatically released when array_ptr is removed from the stack
std::unique_ptr<char> array_ptr = std::unique_ptr<char>(new char[99]);
// do something with array_ptr
if (condition) {
// no memory leak since the destructor of array_ptr is called
// and memory is thus released
return;
}
}
int main()
{
function();
return 0;
}