0

I have the following code


#include <bits/stdc++.h>

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    ~TreeNode() {
        std::cout << "are we in here";
    }
};


int main()
{
    if(1 < 2) {
        TreeNode *testing = new TreeNode(15);
        //why is the destructor not called here?
    }
    TreeNode *root = new TreeNode(15);
    root->left = new TreeNode(10);
    root->right = new TreeNode(20);
    root->left->left = new TreeNode(8);
    root->left->right = new TreeNode(12);
    root->right->left = new TreeNode (16);
    root->right->right = new TreeNode(25);
    //why  is the destructor not being called here?
}

In the two comments, I was wondering why the destructor are not called there? If I remember correctly, when objects go out of scope, the destructor should be called. However, the TreeNode destructor is never called when the pointers go out of scope of the if statement and when main finishes

danielliucs
  • 61
  • 2
  • 6
  • Dynamically created objects (using `new`) will not automatically be destroyed. You always need to `delete` it manually to ensure its destruction. Used `std::unique_ptr` for owning pointers to allow automatic destruction of objects when their pointer goes out of scope. – François Andrieux Sep 03 '22 at 20:57
  • Because the object was allocated on the heap. – tromgy Sep 03 '22 at 20:57
  • 1
    Suggest using managed pointers (`std::smart_ptr`, `std::unique_ptr`). – lorro Sep 03 '22 at 20:58
  • 1
    That's exactly one of the major reasons [to avoid using `new` directly](https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new). Although, in the case of a data structure with deeply-nested pointer indirections, especially linked lists but also trees, there is no good replacement for `new`, because `std::unique_ptr` and similar have some problems with e.g. recursive deletion. In that case one must simply be very careful to always call `delete` on objects that one has `new`ed. – user17732522 Sep 03 '22 at 21:01
  • Hi JaMiT. I am referring to the TreeNode pointer called testing. If I have a pointer to an object and it goes out of scope, I thought the destructor of the object will get called but this is not the case I guess, I was wondering why this happens. (I have edited the question to make it more clear) – danielliucs Sep 03 '22 at 21:12
  • You are confusing *automatic storage duration* with *dynamic storage duration*. q.v. https://en.cppreference.com/w/cpp/language/storage_duration – Eljay Sep 03 '22 at 21:32
  • 1
    Please read [Why should I not #include ?](https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h) – Jesper Juhl Sep 03 '22 at 22:02
  • @danielliucs That is better, thanks. (But you missed having an `@` in front of my name, so the system did not know to notify me.) – JaMiT Sep 05 '22 at 06:41

2 Answers2

5

One of the golden rules of C++, if you use new on something, you also should delete it. When you use new, you are telling the compiler that you are now in charge of managing that variable's lifecycle.

tomithy
  • 66
  • 3
  • 3
    Technically not the *variable’s* lifecycle but rather the *object’s* (the variable in this code is a pointer, its lifetime is still automatic). – Konrad Rudolph Sep 03 '22 at 21:05
  • Oh I see! So to ensure that I don't leak memory, I would have to call ```delete testing``` at the end of the if statement and then I would also need to call delete on all the pointers at the end of main right? – danielliucs Sep 03 '22 at 21:14
  • @KonradRudolph That is a good point. – tomithy Sep 03 '22 at 21:16
  • @danielliucs Exactly. There are many ways you can manage the pointers and the memory they point to, but in this example that would get the behavior you are expecting. I believe you will still have a memory leak though, you should consider also deconstructing all children of the TreeNode (if they exist) in the destructor. That will not be handled automatically when deleting the root. – tomithy Sep 03 '22 at 21:19
  • 1
    Corollary: never use `new`. (I haven't needed to use `new` in a **real program** in over 11 years now.) Moreso, using `new` is an **advanced C++** technique, that shouldn't be employed by beginners — alas, some C++ courses haven't figured that out yet. – Eljay Sep 03 '22 at 21:28
1

If I remember correctly, when objects go out of scope, the destructor should be called.

Correct, but to add a bit of precision: when an object goes out of scope, the destructor of that object is called.

However, the TreeNode destructor is never called when the pointers go out of scope of the if statement and when main finishes

That is true. When the pointers go out of scope, the destructor of the pointers is called. The destructor of a raw pointer does nothing, which entails not calling the TreeNode destructor, as you observed. To get the behavior you expected, you need a smart pointer; see What is a smart pointer and when should I use one?.
NOTE: Smart pointers give the behavior you expected, but the behavior you expected is not necessarily the behavior you want for your tree. A large tree relying on smart pointers could overwhelm the call stack (i.e. crash). However, you asked "why", not "how to manage memory in this case", so this answer focuses on the "why".

One reason a raw pointer destructor does nothing is that there is nothing it can do, as there are no ownership semantics with raw pointers. For example, a pointer might point to a local variable, in which case calling the destructor of the pointed-to object would be wrong.

int main()
{
    // You probably would not want to declare a TreeNode variable, but it is
    // syntactically valid.
    TreeNode local(15);
    // Let's break into a new scope.
    {
        TreeNode *testing = &local;
        // The pointer goes out of scope here, but we cannot destroy `local` yet...
    }
    // ...because we can still access `local` here.
    local.val = 20;
}

While this is not something you would want to do in the context of your tree, the concept is valid and useful in other scenarios. The lesson is that because raw pointers are so flexible, they cannot attempt to destroy the object to which they point.

In contrast, smart pointers do have ownership semantics, which makes them less flexible than raw pointers. (It is invalid to have a smart pointer point to a local variable.) Smart pointers will call the destructor of the pointed-to object when the last smart pointer to that object goes out of scope.

JaMiT
  • 14,422
  • 4
  • 15
  • 31