0
#include <iostream>

class Dude
{
public:
    int age;

    Dude(int age)
    {
        this->age = age;
    }
};

int main(int argc, char* argv[])
{
    Dude *dude1 = new Dude(21);
    std::cout << dude1->age << '\n';

    delete dude1;

    return 0;
}

Is this the correct way to deallocate the memory of dude1 and to destroy the object dude1?

What memory deallocation and cleanup is the destructor doing if left as the default destructor?

dude1->~Dude();

What does this piece of code do?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Dio
  • 29
  • 3
  • Why do you believe that it may not be correct? – Sam Varshavchik Mar 04 '23 at 01:39
  • 2
    Indeed this is correct, consult the documentation of C++ if you're ever unsure... – Cameron Shearer Mar 04 '23 at 01:46
  • 3
    Better would be to avoid `new` and either just declare an automatic variable or use a smart pointer (see [std::make_unique](https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique)). – Jesper Juhl Mar 04 '23 at 01:48
  • I will look into std::make_unique but what about the second code I posted? dude1->~Dude(); Doesn't seem to do anything. – Dio Mar 04 '23 at 01:54
  • 1
    That's because sometimes [when demons fly out everyone's noses](http://www.catb.org/jargon/html/N/nasal-demons.html) they turn out to be harmless. But, if the class contained non-POD objects, then manually invoking `Dude`'s destructor will be, pretty much, a guaranteed crash. – Sam Varshavchik Mar 04 '23 at 01:56
  • Does this answer your question? [C++ how to delete a structure?](https://stackoverflow.com/questions/4134323/c-how-to-delete-a-structure) – Karl Knechtel Mar 04 '23 at 02:02
  • 2
    "dude1->~Dude(); Doesn't seem to do anything" - unless you are writing *very* expert level code involving placement new or similar, don't ever manually invoke destructors. 99.999% of all C++ programmers never need to manually call a destructor - and the few who do are writing the compilers and standard library implementations that the rest of us use. – Jesper Juhl Mar 04 '23 at 02:03
  • 1
    "Is this the correct way to deallocate the memory of dude1 and to destroy the object dude1?" It's correct *for how you have allocated it*, but there are better ways. "What does this piece of code do?" Mainly, it makes experts cry when they try to figure out exactly where they should start trying to explain things to you. It is something that should almost never be written, except by the authors of the standard library, who will provide a tool to hide that ugliness from you with zero overhead. But try https://stackoverflow.com/questions/53262269 and https://isocpp.org/wiki/faq/dtors . – Karl Knechtel Mar 04 '23 at 02:05

1 Answers1

5

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;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • If that is the case then would my code be better if for some reason I wanted manual control of when exactly I wanted to delete the object? – Dio Mar 04 '23 at 02:12
  • 1
    @Dio • you can use a `std::unique_ptr` to manually control exactly when you want to delete the object. – Eljay Mar 04 '23 at 02:42
  • 1
    @Dio `new` and `delete` allow you to have more control of when an object is created and destroyed, yes. Using a smart pointer just makes that process *safer*, but it doesn't change the decision to use dynamic creation in the first place. Regarding Eljay's comment, you don't use a `unique_ptr` to control manual creation/destruction, only to automate the destruction. It would be more accurate to say that you can ALSO manually tell a `unique_ptr` when to destroy the object it manages, if you need to do so before the `unique_ptr` is itself destroyed. – Remy Lebeau Mar 04 '23 at 02:53