简体   繁体   中英

Moving semantics of unique_ptr

Lets consider the following piece of code:

template<typename T>
void f(std::unique_ptr<T>&& uptr) { /*...*/ }

In another function:

void g()
{
    std::unique_ptr<ANY_TYPE> u_ptr = std::make_unique<ANY_TYPE>();
    f(std::move(u_ptr));
X:  u_ptr->do_sth(); // it works, I don't understand why. Details below.
}

I don't understand why u_ptr in line X is still alive. After all I forced him to be moved (std::move).

---EDIT---
Ok, so now:
The code is still working:

class T{
    public:
    T(){}

    void show(){
        std::cout << "HEJ!\n";
    }
};

void f(std::unique_ptr<T> ref){
   ref->show();   
}

int main()
{
    std::unique_ptr<T> my;
    my->show();
    f(std::move(my));
    my->show(); // How is it possible. Now, f takes unique_ptr by value 


    return 0;
}

You didn't show us that code to function f , but presumably it didn't move the pointer, even though it had permission to.

You passed the unique_ptr by reference. If function invocation actually moved it, then the function couldn't use it because it would be gone before the function had a chance to.

If you want function invocation to actually move the pointer, you need to pass the pointer by value, not be reference. That value would be a unique_ptr for it to be moved into. In that case, you should declare the function as taking a std::unique_ptr<T> instead of a std::unique_ptr<T>&& . Then you can actually invoke the move constructor when you call the function.

Update : With your latest change, the unique_ptr would no longer reference any valid object due to the move construction. You just never check that it does. Invoking a non-virtual method that doesn't access any member variables can work just the same whether the object is valid or destroyed because it doesn't need anything from the object. You also never made the unique_ptr actually point to anything.

Instead, make the unique_ptr point to something. After it's moved, try calling a virtual function or accessing a member whose value is changed by the destructor. Like this:

#include <iostream>
#include <memory>

class T{
    public:
    T() : valid (true) {}
    ~T() { valid = false; }

    bool valid;

    void show(){
        std::cout << "HEJ! " << valid << std::endl;
    }
};

void f(std::unique_ptr<T> ref){
   ref->show();   
}

int main()
{
    std::unique_ptr<T> my (new T); // Make it point to a new object
    my->show();
    f(std::move(my));
    my->show(); // Try to access


    return 0;
}

The reason why your call to show doesn't crash is because it doesn't use the this pointer (it doesn't try to modify or access a data member).

Try this:

class T{
    public:
    int v;
    T(){}

    void show(){
        v = 0;
        std::cout << "HEJ!\n";
    }
};

void f(std::unique_ptr&& ref)

This is the answer when you initially had your f function taking a rvalue reference && .

Your function takes a rvalue reference . Therefore, no new unique_ptr object is created yet, you are simply passing a reference .

Inside your f function, if you create aa local unique_ptr , with the parameter uptr , then finally uptr will be moved to create that new object.

template<typename T>
void f(std::unique_ptr<T>&& uptr)
{
     //move uptr into local_unique_ptr 
     //note that we have to use move again
     //because uptr has a name, therefore its a lvalue.
     auto local_unique_ptr = std::unique_ptr<T>(std::move(uptr));
}

The important thing to always know is that std::move is simply a static_cast .

If you pass a lvalue to std::move , it returns a rvalue . If you pass a rvalue , it returns a rvalue . That's it.

in the line f(std::unique_ptr<T>&& uptr) uptr is not an object - it's a reference. a reference which capable to catch temporeries and mutate them.

it's like asking why doesn't the object get cloned in the next example

void func(std::string& str);
std::string str_ = "yyy";
func(str_);

str_ is passed by "regular" reference and won't get copied - this is what pass by reference means.

std::move only cast l-value to r-value-reference, which uptr in f(std::unique_ptr<T>&& uptr) can reference, it's a reference referencing an object. opposed to the common conception, std::move won't do any moving by itself, only casts the object to r-value-reference for the move constructor/assg. operator to kick in.

here, the pointer still holds valid data since it was not moved, only casted to r-value-reference.

if you want the object to move you have to declare the parameter as object, not reference : f(std::unique_ptr<T> uptr)

In your edit, you have undefiend behaviour, so everything may occure.

Your function f may not in fact move the pointer. Merely taking an object by && does not modify the object.

u_ptr->do_sth() may invoke a static member function or a member function that does not access the object ( this ) and this is why it does not crash.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM