应用std :: move

时间:2015-06-21 10:35:01

标签: c++ std move-semantics

我试图理解std :: move和rvalues在C ++ 11中是如何工作的。我在这样的教程中看到了类似的例子:

假设我们有这个课程:

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

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

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

修改:问题不重复。重复的候选者更广泛,因为它甚至没有代码片段。

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稍微丢失第一个对象,以避免a2释放a1的意外内存。所以我设置了rv.a = NULL

当我使用类试验的右值复制构造函数,但不在rvalue构造函数中使用行rv.a = NULL时,整数指针aa1a2中显示相同的地址{1}}当我在第return 0行放置一个断点时。那么这与通过引用传递有什么不同呢?看起来我们可以通过引用传递完全相同的东西。

6 个答案:

答案 0 :(得分:6)

没有

std::move不动一件事。它只是将对象转换(转换)为右值引用,这可以通过查看典型的实现来看到:

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

请注意,T&& arg是可推广上下文中的通用引用,而不是右值引用本身(如果您想知道&#34; isn&#39; t arg rvalue ref已经?&#34)

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

根据EDIT 2

我想你回答了自己的问题。 想象一下,你在课堂上有两个构造函数,移动和复制; xvalue的作用是让你在调用时选择第一个

std::move

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

trial a2(std::move(a1));

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

另一方面,当调用移动构造函数时,你基本上告诉编译器&#34;嘿,我不再使用trial(trial& rv) { this->a = (int*)malloc(sizeof(int)); this->a = rv.a; } 了,尽力而为#34;并且你的移动建设被称为你移植&#34; a1的{​​{1}}资源。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

std::move根据提供的对象返回右值引用。现在可以将这个新创建的右值引用传递给将右值引用作为void foo (Obj&& rvalue_reference)等参数的函数。

如果不详细了解右值参考是什么,你真正需要知道的是rvalues会立即过期而不会再被使用。这允许库编写者做出某些他们无法做出的优化假设。

例如:

让我们采取如下函数:

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

在上面的函数中,我们必须创建一个全新的字符串来返回,因为我们的快乐用户可能仍然想要使用原始字符串。

在功能中:

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

我们可以将str的内部存储添加到Full Stop并返回。这是安全的,因为右值引用是临时对象,因此不需要保留它们以供以后使用。

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

有关右值参考的更多信息,请查看here

答案 4 :(得分:0)

  

应用std :: move后,对象实例会发生什么?&#34;

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

  

&#34;在运行main函数的第二行之后,object1会发生什么?&#34;

您点击范围退出},这会触发析构函数调用。首先在object2上,然后在object1上。

  

如果object1的记忆是&#34;移动&#34;到object2,这个拷贝构造函数有什么意义?因为我们正在失去object1的记忆只是为了在内存中的不同位置获得完全相同的值?这是什么用例?

将其视为专业化。虽然真正的复制构造函数允许您复制一个对象(深入到它的叶子,例如在进行object1object2的分配时),这可能非常非常昂贵,但移动构造函数使您能够只需复制其成员的指针即可快速传输状态。从函数返回时,这会派上用场。

以下是一个例子:

#include <string>
#include <iostream>

using namespace std;

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

Person give_it_here(string* name) {
    return name == nullptr ? Person(string("Chuck")) : Person(*name); // Here's some evalutation required to prevent compiler optimization (for the sake of understanding)
}

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

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

    {
        Person object1 = give_it_here(&string("Jack")); // Let some other function create the object and return it to us.
    }

    return 0;
}

代码打印

Constructing John
Moving John
Destructing John (the moved one)
Destructing nobody (was John)
Constructing James
Copying James
Destructing Copy of James
Destructing James
Constructing Jack
Moving Jack
Destructing nobody (was Jack)
Destructing Jack (the moved one)

答案 5 :(得分:0)

注意你的移动构造函数的定义,对于下面的例子

#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的作用域之后被释放,所以出现了segmentation fault。