0

Im having difficulty describing this problem succinctly so be kind.

I have a Tree object that has an attribute root which is a pointer to a node object. When I initialize the Tree object the root is unknown so i assign it to a nullptr.

In a function after some computation I find the root node of a complete binary tree. I now want to hand this value over to my Tree.root pointer. However since this function is removed from the stack after execution and Tree.root pointer appears empty when I run it later.

class Tree{
    public:
        Node *root;
        Tree(){
            root = nullptr;
        }
};
void worker(Tree *t){
    // Perform some computation



// Since the var rootFound only exists in this function. 
// After executing doesn't the memory address reallocated 
// and therefore the root points to an unknown memory address?
    t-> root = &rootFound;

}
int main(){
    Tree t{};
    Tree *ptr = &t;
    worker(t);
//    t pointer is null
    return 0;
}

I was thinking I could assign the root pointer found by the function to the heap (use new ) and then assign my Tree pointer to it but Im not sure how to go about deleting this value. Also since any node has left and right Node pointer Im not sure if the pointers lose the memory address they are pointing too or if they too will be added to the heap.

I could also just be overthinking this.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
Josh W
  • 29
  • 4
  • What is `rootFound` and how do you get it? – Guillaume Racicot Nov 05 '21 at 17:16
  • 1
    Also as far as I know, `&t` is never null since it's a stack variable – Guillaume Racicot Nov 05 '21 at 17:17
  • 1
    Your comment in the worker() function is correct. `t-> root = &rootFound;` the pointer would be a dangling pointer if `rootFound` is a local variable in the worker function. The local variable will no longer exist when worker() ends. You can not use the tree after this function ends. – drescherjm Nov 05 '21 at 17:18
  • @GuillaumeRacicot that isnt important I explained in my comments and notes that the rootFound is a Node pointer to a complete binary tree. I am trying to assign this local found root value to my tree root. – Josh W Nov 05 '21 at 17:27
  • @JoshW the way you declare it is important, and will determine if your pointer is dangling or not. I will assume you do `Node rootFound;` for my answer, since you assign `&rootFound` to a `Node*` – Guillaume Racicot Nov 05 '21 at 17:28
  • 1
    Side notes: (1) Preferably don’t assign any class members in the constructor body; that’s what *initializers* are for. (2) The `root` member should *not* be `public`. (3) What you most likely want is `std::unique_ptr root` as a member and another `std::unique_ptr rootFound` as a local variable. Then assign `t->root = std::move(rootFound);`. Constructing `rootFound` using `std::make_unique(...)` (i.e. on the heap) will extend its life span beyond that of the stack frame. (4) Always run it with `valgrind`. It will give you a detailed summary of all memory access issues. – Andrej Podzimek Nov 05 '21 at 17:29
  • @drescherjm Thank you! Now that I am certain that the local variable will be destroyed when worker() ends how can I allow this variable to persist and thereby prevent `t -> root = &rootFound ` from being a tangling pointer? Should I use the heap? And if so how do I deallocate this memory? – Josh W Nov 05 '21 at 17:32
  • @AndrejPodzimek explained the modern way however if you can't use `std::unique_ptr` you will have to allocate your Node using new. And for deallocation make sure your class has a destructor and follows the rule of 3 or 5. Related: [https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – drescherjm Nov 05 '21 at 17:34

1 Answers1

2

Since you posted an incomplete code in your question, I will be forced to assume things.

I will assume your implementation look like this:

void worker(Tree *t){
    Node rootFound;

    // do stuff where eventually rootFound = something

    t->root = &rootFound;
}

In this case, yes, t->root will point to a dead object once worker is finished.

I was thinking I could assign the root pointer found by the function to the heap (use new ) and then assign my Tree pointer to it but Im not sure how to go about deleting this value.

There is two kind of raw pointer in C++: owning pointer and non owning pointer.

If t->root is a owning pointer, it means you will call delete on it.

If t->root is not a owning pointer, it means you will not call delete on it.

Then if it is owning, you can totally do new Node{...} an assign to it.

If on the contrary it is not and you want to create an new tree in this function and delete it later, you will need to give an owning pointer back to the caller, something like this:

Node* worker(Tree* t) {
    Node* rootFound = new Node{}; // create a whole new tree here
    t->root = rootFound; // assign it to the tree
    return rootFound; // return a owning pointer to the caller
}

Then, in your main:

int main(){
    Tree t{};
    Tree *ptr = &t;
    Node* owning = worker(ptr);

    // do stuff with t

    // delete the owning pointer.
    delete owning;
    return 0;
}

Of course, there is a better way to separate owning and non owning pointer.

In modern C++, owning pointer are declared like this: std::unique_ptr<T> and non owning are written T* and assume you don't have to delete it.

If we change your data structure just a little bit to express what pointer is which, it would look something like this:

class Tree{
    public:
        // Here! Root is owning.
        std::unique_ptr<Node> root;
        Tree(){
            root = nullptr;
        }
};

// Tree is non-owning, so we write it just like before.
void worker(Tree* t){
    // ...
}

The neat thing about std::unique_ptr is that is clears memory in its destructor so you don't have to worry about deleting:

int main() {
    // make_unique will call new
    std::unique_ptr<Node> node = std::make_unique<Node>();

    // Here, unique_ptr will call delete
}

So in the end, Tree will clear up itself at the end of main:

int main(){
    Tree t{};
    Tree *ptr = &t;

    // worker can do t->root = std::make_unique<Node>();
    worker(ptr);

    return 0;
    // Here, t.root will call delete if not null
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141