赞
踩
支持移动语义,是现代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&&\n";
- } else {
- std::cout << "param's type is other type\n";
- }
- }
-
- 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++并发编程实战》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。