Is this the correct way to deallocate the memory of dude1 and to destroy the object dude1?
Yes, it is technically correct.
But, it is not the best option. A better option would be to create the Dude object in automatic storage, and not worry about its deallocation/cleanup at all, let the compiler worry about that for you, eg:
int main(int argc, char* argv[])
{
Dude dude1(21);
std::cout << dude1.age << '\n';
return 0;
}
If you must create the object dynamically, at least use a smart pointer like std::unique_ptr so you don't need to delete the object manually, eg:
#include <memory>
int main(int argc, char* argv[])
{
auto dude1 = std::make_unique<Dude>(21);
// prior to C++14, use this instead:
// std::unique_ptr<Dude> dude1(new Dude(21));
std::cout << dude1->age << '\n';
return 0;
}
What memory deallocation and cleanup is the destructor doing if left as the default destructor?
In this example, nothing at all.
In general, when you deal with objects, there is a clear separation of responsibilities between allocation/deallocation and creation/destruction. Memory for the object is reserved first, then the object is created within that memory. Later, the object is destroyed first, and then its memory is released.
When an object has automatic storage duration, its memory is reserved within the calling scope (ie, a local variable in a function/block, a data member of a class, etc), and it is created within that scope's memory. When that scope ends (ie, the function/block ends, the containing object is destroyed, etc), the scoped object is automatically destroyed and its memory is released.
Thus, in your example, the default destructor of Dude does nothing, since there is nothing for it to destroy.
Dude has a single int data member, which is created with automatic storage duration within Dude. When a Dude object is created, space for the int is included in the memory allocation for the Dude object, but the int is not valid for use until the Dude object is constructed within that memory. Later, when the Dude object is destroyed, the int becomes invalid for use when the Dude object's destructor is called, and then the memory for the int disappears when the memory for the Dude object is deallocated.
In my first example above, the Dude object has automatic storage duration. Its scope is main(), so the compiler reserves memory inside the stack frame of main() to hold the Dude object, and then creates the Dude object (ie, calls its constructor) within that memory. When main() exits, the Dude object goes out of scope and is destroyed (ie, its destructor is called), and its memory is released when the stack frame is cleaned up.
In my second example above, the Dude object has dynamic storage duration. Its scope is dynamic memory. new allocates dynamic memory to hold the Dude object, and then creates the Dude object (ie, calls its constructor) within that memory. Later, delete destroys the Dude object (ie, calls its destructor) and deallocates the dynamic memory that new had allocated.
Note that the int inside of Dude always has automatic storage duration. Its scope is Dude, so it gets allocated wherever a Dude object is allocated, and is deallocated whenever the Dude object is deallocated. Whether that be in automatic memory (ie, the stack) or in dynamic memory (ie, the heap).
dude1->~Dude();
What does this piece of code do?
It explicitly calls the object's destructor, like any other method call. However, DO NOT DO THIS, except for objects that have been constructed with placement-new, eg:
int main(int argc, char* argv[])
{
// FYI, this is not the correct way to ensure the allocated memory
// is satisfactory for creating an object in it, this is simplified
// just for demonstration purposes!
char *buffer = new char[sizeof(Dude)];
Dude *dude1 = new (buffer) Dude(21);
std::cout << dude1->age << '\n';
dude1->~Dude();
delete[] buffer;
return 0;
}