【问题标题】:What happens when std::move is called on a rvalue reference?在右值引用上调用 std::move 会发生什么?
【发布时间】:2020-11-16 05:24:44
【问题描述】:

考虑以下 c++ 程序:

string construct(string&& s) {
    // Passing a r-value reference as an argument to the assignment operator
    string constructed = s;
    return constructed;
}

int main() {
    string original = "Hello";
    string temp1 = construct(std::move(original));

    printf("%s\n", original.c_str()); // Prints "Hello", so original has not changed
    return 0;
}

现在我执行的一个小更改是在 r 值引用参数上调用 std::move:

string constructWithMove(string&& s) {
    // Passing a cast to an r-value reference using r-value reference as an argument.
    string constructed = std::move(s);
    return constructed;
}

int main() {
    string original = "Hello";
    string temp = constructWithMove(std::move(original));

    printf("%s\n", original.c_str()); // Prints "", original is set to the empty string, WHY???
    return 0;
} 

所以看起来将 r 值引用转换为 r 值引用会引发一些特殊情况。 为什么在第一种情况下原始字符串保留了它的值而在第二种情况下没有

【问题讨论】:

  • 现在我执行的一个小更改是在 r 值引用参数上调用 std::move 不,这不是你所做的。您在左值引用参数上调用了 std::move。
  • @Eljay string&& 不是右值引用吗?
  • 参数通过引用接受一个右值参数。局部参数是命名的,所以不是右值,需要std::moved作为右值使用。
  • 我认为您缺少的一点是,在string constructed = s; 之后,s 必须 保持有效并且不能 从中移出。毕竟,下一行代码可能是string constructed2 = s;。但是,在string constructed = std::move(s); 中,您已授予从s 移动以构造新字符串的权限,因此它可能会将s 更改为有效但不同的状态。
  • @eerorika 也许应该改写:你不能在引用类型的参数上调用函数因为没有引用类型的表达式。在constructWithMove 中,表达式sstring 类型的左值。 std::move 使用左值 string 调用。声明string &&s 影响绑定到s 的内容(在调用点),但不影响s 在函数体内的行为。实际上在右值上调用std::move 将是std::move(std::move(s)) 或类似std::move(std::string(""))

标签: c++ c++11 move move-semantics rvalue-reference


【解决方案1】:

在右值引用上调用 std::move 会发生什么?

std::move 将参数转换为右值引用,然后返回。

std::move(r_value_reference_argument) 未定义

没有。


// Prints "", original is set to the empty string, WHY???

这里发生了什么?

因为std::move 的结果是一个 r 值(更具体的 x 值)。并被传递给std::string 的构造函数,它调用移动构造函数。您正在观察一个被移动的字符串。

请注意,这种移动会使原始字符串处于未指定状态。它不能保证是空的,甚至与它以前包含的内容也不相同。也不保证不为空。


好的,但是为什么在第一种情况下原始字符串不为空?

因为s 是一个左值1,因此使用了复制构造函数。复制构造函数不修改原始字符串。

1 简单的经验法则:如果它是一个名称,那么它就是一个左值。

【讨论】:

  • 好的,但是为什么在第一种情况下原始字符串不为空?我稍微修改了一下问题。
  • @mercury0114, s 是一个左值。它的类型无关紧要。
  • @mercury0114:因此,string constructed = s; 调用复制构造。除非您明确使用std::move,否则编译器不会从它移开,因为函数中的后面一行也可能引用s 并期望它仍处于作为函数参数传递的状态。使用 std::move() 有效地保证了您理解并相应编码的编译器。
【解决方案2】:
string constructed = s;

这不会导致移动,因为s 不是右值。它是一个右值引用,但不是一个右值。如果它有名称,则它不是右值。 Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.

string constructed = std::move(s);

这会导致移动,因为std::move(s) 是一个右值:它是一个临时的并且它的类型不是左值引用。

程序中没有其他动作(std::move 不是动作,而是演员表)。

【讨论】:

  • std::move(s) 是右值还是 右值引用?我认为std::move 会强制转换为 r-value-reference,不是吗?
  • @mercury0114 它是一个右值,因为它是一个临时的。它的类型是“对字符串的右值引用”。这两个属性是正交的。
【解决方案3】:

// Prints "", original is set to the empty string, WHY???

因为你移动了它。

【讨论】:

  • 确切地说,已被移动-from 解释了为什么它允许清空,但并不要求如此。移出值的一般期望是它将保持在有效状态以进行销毁。我个人认为设计自己的可移动类型是可取的,这样它们对于重新分配或clear() 然后重用都是有效的,但这是实现质量的问题。对于没有记录移出对象语义的类型,您应该只依赖“可破坏性”而不假设或依赖具有任何特定值或可重用的移出对象。
  • (特别是,一些指向动态分配内存的对象可能会交换缓冲区,因此目标对象中的任何文本最终都会在移动对象中,作为提供移动对象的一种懒惰方式具有一些堆分配内存的对象,以防被重用,并延迟释放)。
  • 抛出异常怎么样?
  • if (ive_been_moved) throw std::logic_error("hammer error: can't touch this");
  • @super:你从不希望析构函数抛出异常,所以如果在 move-then-destruct 处理中可能抛出一些东西,你肯定希望在“移动时间”。 Elijay 对对象跟踪/检查移动状态的暗示很少可取:只是一个可能出错的额外状态/逻辑的全新表面;在可能的情况下,只需将对象置于“正常”状态,预期的操作将起作用或抛出或其他任何事情。
猜你喜欢
  • 1970-01-01
  • 2017-01-16
  • 2020-10-19
  • 1970-01-01
  • 1970-01-01
  • 2015-07-15
  • 2021-11-13
  • 1970-01-01
相关资源
最近更新 更多