【问题标题】:What is std::move(), and when should it be used?什么是 std::move(),什么时候应该使用它?
【发布时间】:2011-03-25 17:11:45
【问题描述】:
  1. 这是什么?
  2. 它有什么作用?
  3. 什么时候应该使用它?

好的链接表示赞赏。

【问题讨论】:

标签: c++ c++11 move-semantics c++-faq stdmove


【解决方案1】:

1。 “这是什么?”

虽然std::move() 在技术上是一个函数 - 我会说真的是一个函数。它是编译器考虑表达式值的方式之间的一种转换器

2。 “它有什么作用?”

首先要注意的是std::move() 实际上并没有移动任何东西。它将表达式从lvalue(例如命名变量)更改为xvalue。一个 xvalue 告诉编译器:

你可以掠夺我,移动我持有的任何东西并在其他地方使用(因为反正我很快就会被摧毁)”。

换句话说,当您使用std::move(x) 时,您允许编译器蚕食x。因此,如果x 在内存中有自己的缓冲区 - 在std::move() 之后,编译器可以让另一个对象拥有它。

您也可以从prvalue 移动(例如您正在传递的临时地址),但这很少有用。

3。 “什么时候应该使用?”

问这个问题的另一种方式是“我会为了什么而蚕食现有对象的资源?”好吧,如果您正在编写应用程序代码,那么您可能不会对编译器创建的临时对象进行过多处理。所以主要你会在构造函数、操作符方法、标准库算法等函数等地方执行此操作。在这些地方,对象会自动创建和销毁很多。当然,这只是经验法则。

典型的用途是将资源从一个对象“移动”到另一个对象,而不是复制。 @Guillaume 链接到 this page,其中有一个简单的简短示例:用更少的复制交换两个对象。

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

使用 move 可以交换资源而不是复制它们:

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

想想当T 是大小为n 的vector&lt;int&gt; 时会发生什么。在第一个版本中,您读取和写入 3*n 个元素,在第二个版本中,您基本上只读取和写入指向向量缓冲区的 3 个指针,以及 3 个缓冲区的大小。当然T的班级需要知道怎么搬家;你的班级应该有一个移动赋值运算符和一个 T 班级的移动构造函数才能工作。

【讨论】:

  • 很长一段时间我都听说过这些移动语义,但我从未研究过它们。从您给出的描述来看,它似乎是浅拷贝而不是深拷贝。
  • @TitoneMaurice:除了它不是副本 - 因为原始值不再可用。
  • @Zebrafish 你大错特错了。浅拷贝使原件处于完全相同的状态,移动通常会导致原件为空或处于其他有效状态。
  • @rubenvb Zebra 并非完全错误。虽然原始的 cannabilised 对象通常被故意破坏以避免混淆错误(例如,将其指针设置为 nullptr 以表示它不再拥有指针),但事实上整个移动是通过简单地从源中复制一个指针来实现的到目的地(并故意避免对指针做任何事情)确实让人想起浅拷贝。事实上,我什至会说一个移动一个浅拷贝,然后可选地跟随源的部分自毁。 (续)
  • (续) 如果我们允许这个定义(我更喜欢它),那么@Zebrafish 的观察并没有错,只是稍微不完整。
【解决方案2】:

Wikipedia Page on C++11 R-value references and move constructors

  1. 在 C++11 中,除了复制构造函数之外,对象还可以具有移动构造函数。
    (除了复制赋值运算符之外,它们还有移动赋值运算符。)
  2. 如果对象的类型为“右值引用”(Type &amp;&amp;),则使用移动构造函数而不是复制构造函数。
  3. std::move() 是一种类型转换,它产生一个对对象的右值引用,以便从它移动。

这是一种避免复制的新 C++ 方法。例如,使用移动构造函数,std::vector 可以将其指向数据的内部指针复制到新对象,使移动的对象处于已移动状态,因此不会复制所有数据。这将是 C++ 有效的。

尝试在谷歌上搜索移动语义、右值、完美转发。

【讨论】:

  • 移动语义要求移动的对象保持有效,这不是不正确的状态。 (理由:它仍然必须破坏,让它发挥作用。)
  • @GMan:嗯,它必须处于可以安全销毁的状态,但是,AFAIK,它不必可用于其他任何东西。
  • @ZanLynx:对。请注意,标准库还要求移动的对象是可分配的,但这仅适用于标准库中使用的对象,不是一般要求。
  • -1 "std::move() 是 C++11 使用移动语义的方式" 请修复它。 std::move() 不是使用移动语义的方式,移动语义对程序员是透明的。 move 它只是将值从一个点传递到另一个点的强制转换,在该点将不再使用原始左值。
  • 我会走得更远。 std::move 本身“什么都不做”——它的副作用为零。它只是向编译器发出信号,表明程序员不再关心该对象会发生什么。即它向软件的其他部分授予许可以从对象中移动,但它不需要移动它。事实上,右值引用的接收者不必对它将对数据做什么或不做什么做出任何承诺。
【解决方案3】:

当您需要将对象的内容“转移”到其他地方时,您可以使用 move ,而无需进行复制(即内容不重复,这就是为什么它可以用于某些不可复制的对象,例如 unique_ptr )。对象也可以使用 std::move 获取临时对象的内容而不进行复制(并节省大量时间)。

这个链接真的帮助了我:

http://thbecker.net/articles/rvalue_references/section_01.html

如果我的回答来得太晚了,我很抱歉,但我也在为 std::move 寻找一个好的链接,我发现上面的链接有点“严肃”。

这里强调r值引用,在什么情况下你应该使用它们,我认为它更详细,这就是我想在这里分享这个链接的原因。

【讨论】:

  • 不错的链接。我总是发现维基百科文章和其他我偶然发现的链接相当混乱,因为它们只是向你抛出事实,让你弄清楚实际含义/基本原理是什么。虽然构造函数中的“移动语义”相当明显,但所有关于传递 &&-values 的细节都不是......所以教程式的描述非常好。
【解决方案4】:

问:std::move 是什么?

答:std::move() 是 C++ 标准库中的一个函数,用于强制转换为右值引用。

简单地说std::move(t) 相当于:

static_cast<T&&>(t);

右值是一个临时值,不会在定义它的表达式之外持续存在,例如从不存储在变量中的中间函数结果。

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

N2027: "A Brief Introduction to Rvalue References" 中给出了 std::move() 的实现,如下所示:

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

如您所见,std::move 返回 T&amp;&amp;,无论是使用值 (T)、引用类型 (T&amp;) 还是右值引用 (T&amp;&amp;) 调用。

问:它有什么作用?

A:作为一个演员,它在运行时不做任何事情。仅在编译时告诉编译器您希望继续将引用视为右值。

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

做什么:

  • 复制参数
  • 调用复制构造函数
  • 更改参数对象

问:什么时候使用?

答:如果您想使用非右值(临时表达式)的参数调用支持移动语义的函数,您应该使用std::move

这为我引出了以下后续问题:

  • 什么是移动语义?与复制语义相比,移动语义是一种编程技术,其中对象的成员通过“接管”而不是复制另一个对象的成员来初始化。这种“接管”只对指针和资源句柄有意义,它们可以通过复制指针或整数句柄而不是底层数据来廉价地传输。

  • 什么样的类和对象支持移动语义?作为开发人员,您可以在自己的类中实现移动语义,如果这些可以从转移其成员而不是复制它们中受益的话。一旦您实现了移动语义,您将直接受益于许多库程序员的工作,他们添加了对有效处理具有移动语义的类的支持。

  • 为什么编译器不能自己解决?除非您这么说,否则编译器不能只调用函数的另一个重载。您必须帮助编译器选择是调用函数的常规版本还是移动版本。

  • 在哪些情况下我想告诉编译器它应该将变量视为右值?这很可能发生在模板或库函数中,您知道可以挽救中间结果(而不是分配新实例)。

【讨论】:

  • Big +1 用于 cmets 中具有语义的代码示例。其他最重要的答案使用“move”本身定义了 std::move - 并没有真正澄清任何事情! --- 我认为值得一提的是,不复制参数意味着无法可靠地使用原始值。
【解决方案5】:

std::move 本身并没有真正做太多。我以为它调用了对象的移动构造函数,但它实际上只是执行类型转换(将左值变量转换为右值,以便所述变量可以作为参数传递给移动构造函数或赋值运算符)。

所以 std::move 只是用作使用移动语义的先驱。移动语义本质上是一种处理临时对象的有效方式。

考虑对象A = B + (C + (D + (E + F)));

这是一个好看的代码,但是 E + F 产生了一个临时对象。然后 D + temp 产生另一个临时对象,依此类推。在类的每个普通“+”运算符中,都会发生深拷贝。

例如

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

在这个函数中创建临时对象是没有用的——这些临时对象无论如何都会在行尾被删除,因为它们超出了范围。

我们宁愿使用移动语义来“掠夺”临时对象并做类似的事情

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

这避免了不必要的深拷贝。参考示例,现在唯一发生深度复制的部分是 E + F。其余部分使用移动语义。还需要实现移动构造函数或赋值运算符,将结果赋值给A。

【讨论】:

  • 您谈到了移动语义。您应该在答案中添加 std::move 的使用方式,因为问题涉及到这一点。
  • @Koushik std::move 并没有做太多 - 但用于实现移动语义。如果你不知道 std::move,你可能也不知道 move 语义
  • “没有做太多”(是的,只是静态转换到右值引用)。它实际上做了什么,它做了什么是OP所要求的。您不需要知道 std::move 是如何工作的,但您必须知道移动语义的作用。此外,“但用于实现移动语义”则相反。知道移动语义,你就会明白 std::move 否则不会。 move 只是有助于移动,它本身使用移动语义。 std::move 只是将其参数转换为右值引用,这是移动语义所需要的。
  • “但是 E + F 产生一个临时对象” - 运算符 + 从左到右,而不是从右到左。因此B+C 将是第一个!
  • 只有你的回答向我解释了
【解决方案6】:

“它是什么?”“它有什么作用?”上面已经解释过了。

我将举一个“什么时候应该使用”的例子。

例如,我们有一个类,里面有很多资源,比如大数组。

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

测试代码:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

输出如下:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

我们可以看到std::movemove constructor 使转换资源变得容易。

std::move 还有什么用处?

std::move 在对元素数组进行排序时也很有用。许多排序算法(例如选择排序和冒泡排序)通过交换元素对来工作。以前,我们不得不求助于复制语义来进行交换。现在我们可以使用移动语义,效率更高。

如果我们想将一个智能指针管理的内容移动到另一个智能指针,它也很有用。

引用:

【讨论】:

    【解决方案7】:

    std::move 本身除了static_cast 之外什么都不做。根据cppreference.com

    它完全等同于将静态转换为右值引用类型。

    因此,这取决于您在move 之后分配给的变量的类型,如果该类型具有带右值参数的constructorsassign operators,它可能窃取 em> 原始变量的内容,因此,它可能会将原始变量留在unspecified state中:

    除非另有说明,否则所有已被移动的标准库对象都处于有效但未指定的状态。

    因为对于整数和原始指针等内置字面量类型没有特殊的move constructormove assign operator,所以它只是这些类型的简单副本。

    【讨论】:

      【解决方案8】:

      这是一个完整的例子,使用 std::move 作为一个(简单的)自定义向量

      预期输出:

       c: [10][11]
       copy ctor called
       copy of c: [10][11]
       move ctor called
       moved c: [10][11]
      

      编译为:

        g++ -std=c++2a -O2 -Wall -pedantic foo.cpp
      

      代码:

      #include <iostream>
      #include <algorithm>
      
      template<class T> class MyVector {
      private:
          T *data;
          size_t maxlen;
          size_t currlen;
      public:
          MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
          MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }
      
          MyVector<T> (const MyVector& o) {
              std::cout << "copy ctor called" << std::endl;
              data = new T [o.maxlen];
              maxlen = o.maxlen;
              currlen = o.currlen;
              std::copy(o.data, o.data + o.maxlen, data);
          }
      
          MyVector<T> (const MyVector<T>&& o) {
              std::cout << "move ctor called" << std::endl;
              data = o.data;
              maxlen = o.maxlen;
              currlen = o.currlen;
          }
      
          void push_back (const T& i) {
              if (currlen >= maxlen) {
                  maxlen *= 2;
                  auto newdata = new T [maxlen];
                  std::copy(data, data + currlen, newdata);
                  if (data) {
                      delete[] data;
                  }
                  data = newdata;
              }
              data[currlen++] = i;
          }
      
          friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
              auto s = o.data;
              auto e = o.data + o.currlen;;
              while (s < e) {
                  os << "[" << *s << "]";
                  s++;
              }
              return os;
          }
      };
      
      int main() {
          auto c = new MyVector<int>(1);
          c->push_back(10);
          c->push_back(11);
          std::cout << "c: " << *c << std::endl;
          auto d = *c;
          std::cout << "copy of c: " << d << std::endl;
          auto e = std::move(*c);
          delete c;
          std::cout << "moved c: " << e << std::endl;
      }
      

      【讨论】:

        猜你喜欢
        • 2011-09-30
        • 2011-01-18
        • 2010-09-17
        • 2011-08-18
        • 2017-03-21
        • 1970-01-01
        • 1970-01-01
        • 2017-03-23
        相关资源
        最近更新 更多