您现在的位置是:首页 >其他 >现代C++技术研究(7)---std::move的使用场景网站首页其他
现代C++技术研究(7)---std::move的使用场景
简介现代C++技术研究(7)---std::move的使用场景
支持移动语义,是现代C++的主要语言特性之一,std::move本质上就是把一个变量强转成右值引用。在gcc的源码中,std::move的实现如下:
template<typename _Tp>
_GLIBCXX_NODISCARD
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
通常,我们在程序中使用std::move都是为了触发移动构造函数或移动赋值运算符的调用,举个例子:
#include <string>
int main()
{
std::string str = "123";
std::string str2 = std::move(str);
return 0;
}
显然,通过std::move触发了移动构造函数。
如果等号的右侧是一个临时对象,则不需要调用std::move,这种场景,编译器会自动触发移动操作:
#include <string>
int main()
{
std::string str;
str = std::string("123");
return 0;
}
通过对应的汇编代码,我们确认,调用了std::string的移动赋值运算符:
.LC0:
.string "123"
main:
stp x29, x30, [sp, -112]!
mov x29, sp
str x19, [sp, 16]
add x0, sp, 32
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string() [complete object constructor]
add x0, sp, 96
str x0, [sp, 104]
nop
nop
add x0, sp, 96
add x3, sp, 64
mov x2, x0
adrp x0, .LC0
add x1, x0, :lo12:.LC0
mov x0, x3
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) [complete object constructor]
add x1, sp, 64
add x0, sp, 32
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&)
原因其实也容易理解,临时对象就是右值,编译器对于右值的处理方式,就是调用移动构造函数或者移动赋值运算符。
如果一个函数的传入参数是常量,则调用std::move将无法触发移动构造:
#include <string>
int main()
{
const std::string str = "123";
std::string str2 = std::move(str);
return 0;
}
这种场景下,编译器生成代码实际上调用的是std::string的拷贝构造函数:
.LC0:
.string "123"
main:
stp x29, x30, [sp, -112]!
mov x29, sp
str x19, [sp, 16]
add x0, sp, 96
str x0, [sp, 104]
nop
nop
add x0, sp, 96
add x3, sp, 64
mov x2, x0
adrp x0, .LC0
add x1, x0, :lo12:.LC0
mov x0, x3
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) [complete object constructor]
add x0, sp, 96
bl std::__new_allocator<char>::~__new_allocator() [base object destructor]
nop
add x0, sp, 64
bl std::remove_reference<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>::type&& std::move<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
mov x1, x0
add x0, sp, 32
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) [complete object constructor]
mov w19, 0
add x0, sp, 32
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
add x0, sp, 64
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov w0, w19
b .L7
mov x19, x0
add x0, sp, 96
bl std::__new_allocator<char>::~__new_allocator() [base object destructor]
nop
mov x0, x19
bl _Unwind_Resume
mov x19, x0
add x0, sp, 64
bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
mov x0, x19
bl _Unwind_Resume
.L7:
ldr x19, [sp, 16]
ldp x29, x30, [sp], 112
ret
DW.ref.__gxx_personality_v0:
.xword __gxx_personality_v0
这个也容易理解,移动构造函数可能会修改入参,因为定义为常量,不能修改,所以不能调用移动构造函数,但是拷贝构造函数的入参是可以接受常量的。实际上std::move的类型强转是成功的,转型的结果就是常量右值引用:
#include <string>
#include <iostream>
template<typename T> void f(T&& param) // universal reference
{
using ParamType = T&&;
bool isCnstRValRef = std::is_same<ParamType, const std::string&&>::value;
if (isCnstRValRef) {
std::cout << "param's type is const std::string&&
";
} else {
std::cout << "param's type is other type
";
}
}
int main()
{
const std::string str = "123";
f(std::move(str));
std::string str2 = std::move(str);
return 0;
}
测试结果就是const std::string&&:
[root@192 moderncpp]# ./test_move
param's type is const std::string&&
总结一下:
使用std::move就是为了把入参强转为右值,通常这样做是为了触发移动构造函数的调用,大部分场景都是OK的,但是,如果入参是常量,不要加std::move,因为加了也不会调用移动构造函数;如果入参是临时对象,也不要加std::move,因为不加也会调用移动构造函数。
参考资料:
《Effective Modern C++》
《C++并发编程实战》
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。