为什么 std::move 在这里生成指令?
Why is std::move generating instructions here?
我多次听说 std::move(t)
或多或少只是 static_cast<T&&>(t)
的一种奇特表达方式,不会生成任何指令。
当我现在在 godbolt 上玩 std::move
为了更好地理解移动语义时,我看到它 确实 (或至少可能)生成指令。在这个例子中
#include <iostream>
using namespace std;
struct S {
S() { cout << "default ctor" << endl; }
S(S&& s) {
i = s.i;
s.i = 0;
cout << "move ctor" << endl;
}
int i;
};
void foo(S s) { cout << "Foo called with " << s.i << endl; }
int main() {
S s;
foo(static_cast<S&&>(s));
foo(std::move(s));
}
对 foo
的调用导致以下汇编输出
; ... snip ...
lea rdi, [rbp - 16]
lea rsi, [rbp - 8]
call S::S(S&&) [base object constructor]
lea rdi, [rbp - 16]
call foo(S)
lea rdi, [rbp - 8]
call std::remove_reference<S&>::type&& std::move<S&>(S&)
lea rdi, [rbp - 24]
mov rsi, rax
call S::S(S&&) [base object constructor]
lea rdi, [rbp - 24]
call foo(S)
; ... snip ...
std::remove_reference<S&>::type&& std::move<S&>(S&): # @std::remove_reference<S&>::type&& std::move<S&>(S&)
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov rax, qword ptr [rbp - 8]
pop rbp
ret
有人可以给我解释一下吗?我不太明白这个 std::remove_reference<S&>::type&& std::move<S&>(S&)
函数应该做什么,以及为什么与通常所说的有明显的收缩。
至于std::remove_reference<S&>::type&& std::move<S&>(S&)
Josuttis 在他的 C++ 移动语义中解释了这一点。
基本上它所做的与static_cast<T&&>
类似,但具有类型特征的方式。它允许传入任何值类别(因此,左值或右值引用)然后它切断引用部分并应用于 rvalue-ref 一个。至于额外的说明:任何优化器都应该内联这些调用。
关闭优化并将 foo
定义为 void foo(const S& s);
以减少噪音:
foo(static_cast<S&&>(s));
leaq -4(%rbp), %rax
movq %rax, %rdi
call foo(S const&)
foo(std::move(s));
leaq -4(%rbp), %rax
movq %rax, %rdi
call std::remove_reference<S&>::type&& std::move<S&>(S&)
movq %rax, %rdi
与 -O1
的结果相同:
leaq 12(%rsp), %rdi
call foo(S const&)
您正在编译时未进行优化。因此,您可以准确地看到所写的内容,而无需任何简化或内联函数的尝试。
生成的代码大致等同于 type&& foo(type& x) { return x; }
生成的代码,这就是 move
所做的。
研究在没有打开优化的情况下生成的程序集是徒劳的。
我多次听说 std::move(t)
或多或少只是 static_cast<T&&>(t)
的一种奇特表达方式,不会生成任何指令。
当我现在在 godbolt 上玩 std::move
为了更好地理解移动语义时,我看到它 确实 (或至少可能)生成指令。在这个例子中
#include <iostream>
using namespace std;
struct S {
S() { cout << "default ctor" << endl; }
S(S&& s) {
i = s.i;
s.i = 0;
cout << "move ctor" << endl;
}
int i;
};
void foo(S s) { cout << "Foo called with " << s.i << endl; }
int main() {
S s;
foo(static_cast<S&&>(s));
foo(std::move(s));
}
对 foo
的调用导致以下汇编输出
; ... snip ...
lea rdi, [rbp - 16]
lea rsi, [rbp - 8]
call S::S(S&&) [base object constructor]
lea rdi, [rbp - 16]
call foo(S)
lea rdi, [rbp - 8]
call std::remove_reference<S&>::type&& std::move<S&>(S&)
lea rdi, [rbp - 24]
mov rsi, rax
call S::S(S&&) [base object constructor]
lea rdi, [rbp - 24]
call foo(S)
; ... snip ...
std::remove_reference<S&>::type&& std::move<S&>(S&): # @std::remove_reference<S&>::type&& std::move<S&>(S&)
push rbp
mov rbp, rsp
mov qword ptr [rbp - 8], rdi
mov rax, qword ptr [rbp - 8]
pop rbp
ret
有人可以给我解释一下吗?我不太明白这个 std::remove_reference<S&>::type&& std::move<S&>(S&)
函数应该做什么,以及为什么与通常所说的有明显的收缩。
至于std::remove_reference<S&>::type&& std::move<S&>(S&)
Josuttis 在他的 C++ 移动语义中解释了这一点。
基本上它所做的与static_cast<T&&>
类似,但具有类型特征的方式。它允许传入任何值类别(因此,左值或右值引用)然后它切断引用部分并应用于 rvalue-ref 一个。至于额外的说明:任何优化器都应该内联这些调用。
关闭优化并将 foo
定义为 void foo(const S& s);
以减少噪音:
foo(static_cast<S&&>(s));
leaq -4(%rbp), %rax
movq %rax, %rdi
call foo(S const&)
foo(std::move(s));
leaq -4(%rbp), %rax
movq %rax, %rdi
call std::remove_reference<S&>::type&& std::move<S&>(S&)
movq %rax, %rdi
与 -O1
的结果相同:
leaq 12(%rsp), %rdi
call foo(S const&)
您正在编译时未进行优化。因此,您可以准确地看到所写的内容,而无需任何简化或内联函数的尝试。
生成的代码大致等同于 type&& foo(type& x) { return x; }
生成的代码,这就是 move
所做的。
研究在没有打开优化的情况下生成的程序集是徒劳的。