当前位置:   article > 正文

现代C++技术研究(7)---std::move的使用场景_std::move使用场景

std::move使用场景

支持移动语义,是现代C++的主要语言特性之一,std::move本质上就是把一个变量强转成右值引用。在gcc的源码中,std::move的实现如下:

  1. template<typename _Tp>
  2. _GLIBCXX_NODISCARD
  3. constexpr typename std::remove_reference<_Tp>::type&&
  4. move(_Tp&& __t) noexcept
  5. { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

通常,我们在程序中使用std::move都是为了触发移动构造函数或移动赋值运算符的调用,举个例子:

  1. #include <string>
  2. int main()
  3. {
  4. std::string str = "123";
  5. std::string str2 = std::move(str);
  6. return 0;
  7. }

显然,通过std::move触发了移动构造函数。

如果等号的右侧是一个临时对象,则不需要调用std::move,这种场景,编译器会自动触发移动操作:

  1. #include <string>
  2. int main()
  3. {
  4. std::string str;
  5. str = std::string("123");
  6. return 0;
  7. }

通过对应的汇编代码,我们确认,调用了std::string的移动赋值运算符

  1. .LC0:
  2. .string "123"
  3. main:
  4. stp x29, x30, [sp, -112]!
  5. mov x29, sp
  6. str x19, [sp, 16]
  7. add x0, sp, 32
  8. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string() [complete object constructor]
  9. add x0, sp, 96
  10. str x0, [sp, 104]
  11. nop
  12. nop
  13. add x0, sp, 96
  14. add x3, sp, 64
  15. mov x2, x0
  16. adrp x0, .LC0
  17. add x1, x0, :lo12:.LC0
  18. mov x0, x3
  19. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) [complete object constructor]
  20. add x1, sp, 64
  21. add x0, sp, 32
  22. 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将无法触发移动构造:

  1. #include <string>
  2. int main()
  3. {
  4. const std::string str = "123";
  5. std::string str2 = std::move(str);
  6. return 0;
  7. }

这种场景下,编译器生成代码实际上调用的是std::string的拷贝构造函数:

  1. .LC0:
  2. .string "123"
  3. main:
  4. stp x29, x30, [sp, -112]!
  5. mov x29, sp
  6. str x19, [sp, 16]
  7. add x0, sp, 96
  8. str x0, [sp, 104]
  9. nop
  10. nop
  11. add x0, sp, 96
  12. add x3, sp, 64
  13. mov x2, x0
  14. adrp x0, .LC0
  15. add x1, x0, :lo12:.LC0
  16. mov x0, x3
  17. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) [complete object constructor]
  18. add x0, sp, 96
  19. bl std::__new_allocator<char>::~__new_allocator() [base object destructor]
  20. nop
  21. add x0, sp, 64
  22. 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&)
  23. mov x1, x0
  24. add x0, sp, 32
  25. 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]
  26. mov w19, 0
  27. add x0, sp, 32
  28. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
  29. add x0, sp, 64
  30. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
  31. mov w0, w19
  32. b .L7
  33. mov x19, x0
  34. add x0, sp, 96
  35. bl std::__new_allocator<char>::~__new_allocator() [base object destructor]
  36. nop
  37. mov x0, x19
  38. bl _Unwind_Resume
  39. mov x19, x0
  40. add x0, sp, 64
  41. bl std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
  42. mov x0, x19
  43. bl _Unwind_Resume
  44. .L7:
  45. ldr x19, [sp, 16]
  46. ldp x29, x30, [sp], 112
  47. ret
  48. DW.ref.__gxx_personality_v0:
  49. .xword __gxx_personality_v0

这个也容易理解,移动构造函数可能会修改入参,因为定义为常量,不能修改,所以不能调用移动构造函数,但是拷贝构造函数的入参是可以接受常量的。实际上std::move的类型强转是成功的,转型的结果就是常量右值引用:

  1. #include <string>
  2. #include <iostream>
  3. template<typename T> void f(T&& param) // universal reference
  4. {
  5. using ParamType = T&&;
  6. bool isCnstRValRef = std::is_same<ParamType, const std::string&&>::value;
  7. if (isCnstRValRef) {
  8. std::cout << "param's type is const std::string&&\n";
  9. } else {
  10. std::cout << "param's type is other type\n";
  11. }
  12. }
  13. int main()
  14. {
  15. const std::string str = "123";
  16. f(std::move(str));
  17. std::string str2 = std::move(str);
  18. return 0;
  19. }

测试结果就是const std::string&&:

  1. [root@192 moderncpp]# ./test_move
  2. param's type is const std::string&&

总结一下:

使用std::move就是为了把入参强转为右值,通常这样做是为了触发移动构造函数的调用,大部分场景都是OK的,但是,如果入参是常量,不要加std::move,因为加了也不会调用移动构造函数;如果入参是临时对象,也不要加std::move,因为不加也会调用移动构造函数。

参考资料:

《Effective Modern C++》

《C++并发编程实战》

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/640102
推荐阅读
相关标签
  

闽ICP备14008679号