Consider some class/struct
struct Foo{
int val = 0;
std::unique_ptr<Foo> child_a = NULL;
std::unique_ptr<Foo> child_b = NULL;
Foo(int val_):val(val_){}
~Foo(){std::cout<<"Deleting foo "<<val<<std::endl;}
};
as you might construct in a doubly linked list/binary tree or similar.
Now, consider that in such a list we have several such Foo
s which point to each other in a tree like fashion e.g. through
std::unique_ptr<Foo> root = std::make_unique<Foo>(Foo(0));
root->child_a = std::make_unique<Foo>(Foo(1));
root->child_b = std::make_unique<Foo>(Foo(2));
root->child_a->child_a = std::make_unique<Foo>(Foo(3));
root->child_a->child_b = std::make_unique<Foo>(Foo(4));
root->child_b->child_a = std::make_unique<Foo>(Foo(5));
root->child_b->child_b = std::make_unique<Foo>(Foo(6));
In this context, if I write
root = NULL
then all the linked children get deleted and I get output that looks something like
Deleting foo 0
Deleting foo 1
Deleting foo 3
Deleting foo 4
Deleting foo 2
Deleting foo 5
Deleting foo 6
My question then, is what exactly is happening when I do something like this
root = std::move(root->child_a);
The output in such a situation looks like
Deleting foo 0
Deleting foo 2
Deleting foo 5
Deleting foo 6
leaving only the child_a
branch in place of the original root
as one might hope.
But looking at this I realise I'm not completely sure how the std::move
works under the hood here, and the "expected behavior" really seems to be taking the self-referential move for granted. I had always broadly thought that a move like this
a=std::move(b);
functioned very roughly as
a = NULL;
b.release();
a.get() = b.get();
but of course this can't be right here, because the first NULL
would destruct b
before it could replace the a
which has just been removed.
Instead I imagine something like this is happening
b.release();
c = b.get();
a = NULL;
a.get() = c;
such that b
is moved into some new raw pointer c
so that a
can be deleted without interfering with the original b
.
But it took a bit of thought an experimentation to try to figure out what is going on here, and I'm still not sure, which is a bit unnerving when a) reading code with such uses whilst b) the vast majority of tutorials I can find on unique_ptrs just don't mention what to expect when moving nested pointers to each other.
Can anyone elaborate what is actually happening here and perhaps point me to a good resource?
CodePudding user response:
from cppreference
std::unique_ptr<T,Deleter>::reset
void reset( pointer ptr = pointer() ) noexcept;
Given current_ptr, the pointer that was managed by *this, performs the following actions, in this order:
- Saves a copy of the current pointer old_ptr = current_ptr
- Overwrites the current pointer with the argument current_ptr = ptr
- If the old pointer was non-empty, deletes the previously managed object if(old_ptr) get_deleter()(old_ptr).
std::unique_ptr<T,Deleter>::operator=
unique_ptr& operator=( unique_ptr&& r ) noexcept;
Move assignment operator. Transfers ownership from r to *this as if by calling reset(r.release()) followed by an assignment of get_deleter() from std::forward<Deleter>(r.get_deleter())