【问题标题】:Moving semantics of unique_ptrunique_ptr 的移动语义
【发布时间】:2016-03-09 10:26:39
【问题描述】:

让我们考虑以下代码:

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

在另一个函数中:

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.
}

我不明白为什么u_ptr 中的X 还活着。 毕竟我强迫他被移动(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;
}

【问题讨论】:

    标签: c++ c++14 move-semantics unique-ptr


    【解决方案1】:

    您没有向我们展示函数f 的代码,但大概它没有移动指针,即使它有权限。

    您通过引用传递了unique_ptr。如果函数调用实际上移动了它,那么函数就不能使用它,因为它会在函数有机会之前就消失了。

    如果您希望函数调用实际移动指针,则需要按值传递指针,而不是引用。该值将是 unique_ptr 以便将其移入。在这种情况下,您应该将函数声明为采用std::unique_ptr&lt;T&gt; 而不是std::unique_ptr&lt;T&gt;&amp;&amp;。然后就可以在调用函数的时候实际调用move构造函数了。

    更新:随着您的最新更改,unique_ptr 将由于移动构造而不再引用任何有效对象。你只是从不检查它是否确实如此。调用不访问任何成员变量的非虚拟方法无论对象是有效的还是已销毁的,都可以同样工作,因为它不需要对象中的任何内容。你也从来没有让 unique_ptr 实际上指向任何东西。

    相反,让unique_ptr 指向某个东西。移动后,尝试调用虚函数或访问其值被析构函数更改的成员。像这样:

    #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;
    }
    

    【讨论】:

    • 但是他为什么不移动指针呢?调用函数应该叫move_constructors吗?
    • 您通过引用传递了unique_ptr。没有什么可以移动的构造。如果要调用移动构造函数,则必须按值传递 unique_ptr,而不是按引用传递。然后你可以移动构造那个值。
    • 现在您的代码具有未定义的行为。尝试调用虚函数或访问其值被析构函数更改的成员。 (你这样做了两次,因为my 从不指代T。)
    【解决方案2】:

    f(std::unique_ptr&lt;T&gt;&amp;&amp; uptr)uptr 行中不是一个对象——它是一个引用。一个能够捕捉临时性并改变它们的参考。

    这就像问为什么在下一个示例中没有克隆对象

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

    str_ 通过“常规”引用传递,不会被复制 - 这就是通过引用传递的意思。

    std::move 只将左值转换为右值引用,f(std::unique_ptr&lt;T&gt;&amp;&amp; uptr) 中的uptr 可以引用,它是引用对象的引用。与一般概念相反,std::move 不会自行进行任何移动,只会将对象强制转换为移动构造函数/assg 的 r-value-reference。操作员开始。

    这里,指针仍然保存有效数据,因为它没有被移动,只是被转换为 r-value-reference。

    如果你想让对象移动,你必须将参数声明为对象,而不是引用:f(std::unique_ptr&lt;T&gt; uptr)

    在您的编辑中,您的行为不可靠,因此一切都可能发生。

    【讨论】:

      【解决方案3】:

      您对show 的调用没有崩溃的原因是它没有使用this 指针(它不会尝试修改或访问数据成员)。

      试试这个:

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

      void f(std::unique_ptr&& ref)

      这是您最初让 f 函数获取右值引用时的答案&&

      你的函数需要一个右值reference。因此,还没有创建新的unique_ptr 对象,您只是传递了一个reference

      在您的f 函数中,如果您使用参数uptr 创建一个本地unique_ptr,那么最终uptr 将被移动以创建该新对象。

      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));
      }
      

      始终要知道的重要一点是,std::move 只是一个 static_cast

      如果您将lvalue 传递给std::move,它将返回rvalue。如果您传递rvalue,它会返回rvalue。就是这样。

      【讨论】:

      • 那是因为你的 show 函数不访问或修改数据成员(因此它从不尝试使用 this 指针),就像你调用一个静态函数一样。如果你修改你的 show 函数来修改一个数据成员,它会崩溃。
      • 好的,但是 unique_ptr.get() 应该是 null ptr 所以怎么可能调用那个函数(显示)。也许我们必须知道对象(this)的地址,实际上是空指针。
      • 它是 nullptr。我已经修改了我的答案以使您的程序崩溃。仅仅调用一个带有无效指针的函数不会使其崩溃,除非该函数实际使用了 this 指针。
      【解决方案4】:

      您的函数f 实际上可能不会移动指针。仅通过&amp;&amp; 获取对象不会修改对象。

      u_ptr-&gt;do_sth() 可能会调用静态成员函数或不访问对象 (this) 的成员函数,这就是它不会崩溃的原因。

      【讨论】:

      • "(...) 这就是它不会崩溃的原因。" 虽然它仍然是 UB
      • @PiotrSkotnicki 有见地的评论。吃一块饼干。
      • @PiotrSkotnicki 但是等等!是 undefined 行为,还是 unspecified 行为?这在这里很重要!
      • 这是未定义的行为
      猜你喜欢
      • 1970-01-01
      • 2013-04-07
      • 2014-07-13
      • 2014-12-18
      • 2020-12-23
      • 2016-08-12
      • 1970-01-01
      • 1970-01-01
      • 2014-07-30
      相关资源
      最近更新 更多