应用 std::move 后对象实例会发生什么

What happens to an object instance after applying std::move

我正在尝试了解 std::move 和右值在 C++ 11 中的工作方式。我一直在这样的教程中看到类似的示例:

假设我们有这个 class :

class Class 
{
   public:
      Class(Class &&b)
      {
          a = b.a;
      }
      int *a;
}

int main()
{
    Class object1(5);
    Class object2(std::move(object1));
}

main函数第二行运行之后,object1会发生什么? 如果 object1 的内存是 "moved" 到 object2,那么这个复制构造函数的意义何在?因为我们正在丢失 object1 的内存只是为了在内存中的不同位置获得完全相同的值?这有什么用例?

编辑:问题不是重复的。 duplicative 的候选范围更广,因为它甚至没有代码片段。

Edit2 : 我刚试过这段代码 :

class trial
{
public:
    trial()
    {
        a = (int*)malloc(4);
    }

    trial(trial& rv)
    {
        this->a = rv.a;
        rv.a = NULL;
    }

    int *a;
};

int main() {
    cout << "Program Started" << endl;
    trial a1;
    trial a2(a1);


    return 0;
}

并且我已经检查了a1 和a2 的内部结构。它给出了与此代码完全相同的结果:

class trial
{
public:
    trial()
    {
        a = (int*)malloc(4);
    }

    trial(trial&& rv)
    {
        this->a = rv.a;
        rv.a = NULL;
    }

    int *a;
};

int main() {
    cout << "Program Started" << endl;
    trial a1;
    trial a2(std::move(a1));


    return 0;
}

不同之处在于复制构造函数,其中一个不使用移动语义。只是对象的简单引用。如果我们也通过引用传递,则不会发生复制,但是我们将不得不通过执行 rv.a = NULL 来丢失第一个对象,以避免通过释放 a1 意外释放 a2 的内存。所以我设置了rv.a = NULL

当我使用 class 试验的右值复制构造函数,但不在右值构造函数中使用行 rv.a = NULL 时,整数指针 aa1a2 当我在 return 0 行放置断点时。那么这与仅通过引用传递有何不同?看起来我们可以通过引用传递来做完全相同的事情。

std::move 将通过将左值对象的类型转换为右值引用来实现资源(包括内存)的所有权从 object1 转移到 object2。因此,您的移动复制构造函数或移动赋值运算符可以启动资源传输。那么 obj1 的资源现在是 object2 的资源,如果 object1 是可移动的,在你的情况下它是。

典型的用例是避免复制大型资源(例如成员 std::vector)。如果没有移动语义,它就会被复制。有了它,移动基本上就是一个指针交换,可以快很多。

请注意,已移动到另一个对象的对象必须仍处于有效状态,因为它的析构函数仍将被调用。 因此,在您的示例中,您最好将 a 成员设置为 nullptr,因为它不能再拥有该资源(以避免双重删除)。

没有。

std::move 不动。它只是将对象强制转换(转换)为右值引用,这可以通过查看典型实现来了解:

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

请注意,T&& arg 是可推导上下文中的通用引用,而不是右值引用本身(如果您想知道“arg 不是右值引用吗?”)

使用右值引用的函数,如移动构造函数和移动赋值运算符或带有 && args 的常规函数​​,可以利用此值类别(称为 xvalue 即过期对象) 并通过将数据移出对象来避免开销,使其处于有效但未指定的状态(例如可破坏)。

根据编辑 2

我想你回答了你自己的问题。 假设您在 class 中有两个构造函数,移动和复制; std::move 的作用是让您 select 在呼叫

时成为第一个
trial a2(std::move(a1));

因为你对两者的实现是相同的,他们将做同样的事情。典型的实现会避免在复制构造函数中使用别名:

trial(trial& rv)
{
    this->a = (int*)malloc(sizeof(int));
    this->a = rv.a;
}

这意味着需要执行额外的分配(你只是想要一个副本,为什么要弄乱原来的?)。

另一方面,当调用移动构造函数时,您基本上是在告诉编译器“嘿,我不再使用 a1,尽力而为”,您的移动构造被调用,您“移植" a1 资源到 a2.

std::move returns 一个基于所提供对象的右值引用。这个新创建的右值引用现在可以传递给将右值引用作为参数的函数,如 void foo (Obj&& rvalue_reference).

无需深入了解什么是右值引用,您真正需要知道的是右值预计会立即过期并且不再被使用。这允许库编写者做出某些他们无法做出的优化假设。

例如:

让我们采用这样的函数:

std::string addFullStopToEnd(std::string& str)

在上面的函数中,我们必须为 return 创建一个全新的字符串,因为我们满意的用户可能仍想使用原始字符串。

在函数中:

std::string addFullStopToEnd(std::string&& str)

我们可以只取str的内部存储追加句号和return。这是安全的,因为右值引用是临时对象,因此无需保留它们供以后使用。

因此,从实际的角度来看,如果用 std::move 修改函数参数,则等于声明一旦该参数从函数中 return 就永远不会引用该参数。这样做会导致未定义的行为。

有关右值引用的更多信息,请查看 here

What happens to an object instance after applying std::move?"

没有。之后它将被视为任何其他对象。这意味着析构函数仍将被调用。正如 rems4e 已经提到的那样,您应该转移状态(例如,通过复制指针,因为这很便宜)并让原始对象不引用它以前的资源(如果析构函数试图按应有的方式释放它们)或其他一些已定义的状态。

"After the second line in main function is ran, what happens to object1?"

您命中了范围退出 },这触发了析构函数调用。首先是 object2,然后是 object1

If memory of object1 is "moved" to object2, what is the point of this copy constructor? As we are losing memory of object1 just to get exact same value in a different place in memory? What is the use case of this?

将其视为专业化。虽然真正的复制构造函数使您能够复制一个对象(深入到它的叶子,例如,在将 object1 赋值给 object2 时),这可能非常非常昂贵,但移动构造函数使您能够只需复制其成员的指针即可快速转移状态。当 return 从函数中调用时,这会派上用场。

这是一个例子:

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class Person {
private:
    shared_ptr<string> name;
public:
    Person(shared_ptr<string> name) {
        cout << "Constructing " << *name << endl;
        this->name = name;
    }
    Person(const Person& original) {
        cout << "Copying " << *original.name << endl;
        name = make_shared<string>("Copy of " + *original.name);
    }
    Person(Person&& original) {
        cout << "Moving " << *original.name << endl;
        name = make_shared<string>(*original.name + ", the moved one");
        original.name = make_shared<string>("nobody (was " + *original.name + ")");
    }
    ~Person() {
        cout << "Destroying " << *name << endl;
        name = make_shared<string>();
    }
};

Person give_it_here(shared_ptr<string> name) {
    return Person(name);
}

int main(int argc, char* argv[]) {
    {
        Person p1(make_shared<string>("John"));
        Person p2 = move(p1); // Unnecessarily moving to another variable. It makes no sense.
    }
    cout << endl;

    {
        Person p1(make_shared<string>("James"));
        Person p2 = p1; // Copying here. Could make sense, but it depends.
    }
    cout << endl;

    {
        Person p1 = give_it_here(make_shared<string>("Jack")); // Let some other function create the object and return (move) it to us.
    }

    return 0;
}

代码打印(使用带有 C++11 和 -fno-elide-constructors 的 g++)

Constructing John
Moving John
Destroying John, the moved one
Destroying nobody (was John)

Constructing James
Copying James
Destroying Copy of James
Destroying James

Constructing Jack
Moving Jack
Destroying nobody (was Jack)
Moving Jack, the moved one
Destroying nobody (was Jack, the moved one)
Destroying Jack, the moved one, the moved one

备注:

  • 需要该标志 -fno-elide-constructors 来防止 return 值优化(对于此示例)
  • 由于某些我无法理解的原因,g++ 生成了两个动作而不是最后一个示例中的一个

注意移动构造函数的定义,例如以下示例

#include <iostream>
#include <vector>
using namespace std;

class A{

public:
    int *ptr;

  A(int x){
    // Default constructor
    cout << "Calling Default constructor\n";
    ptr = new int ;
    *ptr = x;
  }

  A( const A & obj){
    // Copy Constructor
    // copy of object is created
    this->ptr = new int;
    // Deep copying
    cout << "Calling Copy constructor\n";
  }

  A ( A && obj){
    // Move constructor
    // It will simply shift the resources,
    // without creating a copy.
     cout << "Calling Move constructor\n";
    this->ptr = obj.ptr;
    obj.ptr = NULL;
  }

  ~A(){
    // Destructor
    cout << "Calling Destructor\n";
    delete ptr;
  }

};

int main() {

  A b = A(2);
  {
      vector <A> vec;

  vec.push_back(std::move(b));
  }
 cout << *(b.ptr);  //Segmentation fault (core dumped)
  return 0;

}

发生段错误,因为b.ptr的内存在vector vec.

的范围之后被释放