• c++ 移动构造方法为什么要加noexcept


    背景:

    最近看了候捷老师的c++的教程, 他说移动构造方法要加noexcept,  在vector扩容的时候, 如果有移动构造方法没有加noexcept,是不会调用的. 个人感觉有些神奇, 这就去查下一探究竟.

    过程:

    测试代码如下:

    1. #include <iostream>
    2. #include <vector>
    3. struct A
    4. {
    5. A(){
    6. std::cout<<"A::A()"<<std::endl;
    7. }
    8. A(const A &a)
    9. {
    10. std::cout<<"A::A(const A&a)"<<std::endl;
    11. }
    12. A(A &&a)
    13. {
    14. std::cout<<"A::A(A &&a)"<<std::endl;
    15. }
    16. A& operator=(const A&a)
    17. {
    18. std::cout<<"operator=(const A&a)"<<std::endl;
    19. return *this;
    20. }
    21. A& operator = (A &&a)
    22. {
    23. std::cout<<"operator =(A&&a)"<<std::endl;
    24. return *this;
    25. }
    26. };
    27. int main()
    28. {
    29. std::vector<A> vecA;
    30. A a;
    31. vecA.push_back(a);
    32. std::cout<<"1"<<std::endl;
    33. vecA.push_back(a);
    34. std::cout<<"2"<<std::endl;
    35. vecA.push_back(a);
    36. std::cout<<"3"<<std::endl;
    37. vecA.push_back(a);
    38. std::cout<<"4"<<std::endl;
    39. return 0;
    40. }

     执行结果如下:

    1. A::A()
    2. A::A(const A&a)
    3. 1
    4. A::A(const A&a)
    5. A::A(const A&a)
    6. 2
    7. A::A(const A&a)
    8. A::A(const A&a)
    9. A::A(const A&a)
    10. 3
    11. A::A(const A&a)
    12. 4

    我们知道vector 是要扩容的, 在A(A &&a) 并没有添加noexcept关键字, 所以扩容的时候,使用的也是拷贝构造方法, 那接下来我们看下加下 noexcept 后了,结果是什么样的

    1. #include <iostream>
    2. #include <vector>
    3. struct A
    4. {
    5. A(){
    6. std::cout<<"A::A()"<<std::endl;
    7. }
    8. A(const A &a)
    9. {
    10. std::cout<<"A::A(const A&a)"<<std::endl;
    11. }
    12. A(A &&a) noexcept
    13. {
    14. std::cout<<"A::A(A &&a)"<<std::endl;
    15. }
    16. A& operator=(const A&a) noexcept
    17. {
    18. std::cout<<"operator=(const A&a)"<<std::endl;
    19. return *this;
    20. }
    21. A& operator = (A &&a)
    22. {
    23. std::cout<<"operator =(A&&a)"<<std::endl;
    24. return *this;
    25. }
    26. };
    27. int main()
    28. {
    29. std::vector<A> vecA;
    30. A a;
    31. vecA.push_back(a);
    32. std::cout<<"1"<<std::endl;
    33. vecA.push_back(a);
    34. std::cout<<"2"<<std::endl;
    35. vecA.push_back(a);
    36. std::cout<<"3"<<std::endl;
    37. vecA.push_back(a);
    38. std::cout<<"4"<<std::endl;
    39. return 0;
    40. }

    执行结果如下:

    1. A::A()
    2. A::A(const A&a)
    3. 1
    4. A::A(const A&a)
    5. A::A(A &&a)
    6. 2
    7. A::A(const A&a)
    8. A::A(A &&a)
    9. A::A(A &&a)
    10. 3
    11. A::A(const A&a)
    12. 4

    在A(A &&a) noexcept 后, 调用的方法就是移动构造方法, 感觉挺不可思议的, 带着这个疑问,我们看下std::vector 源码来找寻答案

    揭秘:

    push_back 源码如下:

    1. template <class _Tp, class _Allocator>
    2. inline _LIBCPP_INLINE_VISIBILITY
    3. void
    4. vector<_Tp, _Allocator>::push_back(const_reference __x)
    5. {
    6. if (this->__end_ != this->__end_cap())
    7. {
    8. __RAII_IncreaseAnnotator __annotator(*this);
    9. __alloc_traits::construct(this->__alloc(),
    10. _VSTD::__to_raw_pointer(this->__end_), __x);
    11. __annotator.__done();
    12. ++this->__end_;
    13. }
    14. else
    15. __push_back_slow_path(__x);
    16. }

    因为我们要看扩容相关的代码,  __push_back_slow_path(__x); 对应的需要扩容要调用的代码

    1. #ifndef _LIBCPP_CXX03_LANG
    2. vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x)
    3. #else
    4. vector<_Tp, _Allocator>::__push_back_slow_path(_Up& __x)
    5. #endif
    6. {
    7. allocator_type& __a = this->__alloc();
    8. __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
    9. // __v.push_back(_VSTD::forward<_Up>(__x));
    10. __alloc_traits::construct(__a, _VSTD::__to_raw_pointer(__v.__end_), _VSTD::forward<_Up>(__x));
    11. __v.__end_++;
    12. __swap_out_circular_buffer(__v);
    13. }

    上边是分配内从,我们重点看下__swap_out_circular_buffer(__v);  把老的元素拷贝新的申请区域上

    1. template <class _Tp, class _Allocator>
    2. void
    3. vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v)
    4. {
    5. __annotate_delete();
    6. __alloc_traits::__construct_backward(this->__alloc(), this->__begin_, this->__end_, __v.__begin_);
    7. _VSTD::swap(this->__begin_, __v.__begin_);
    8. _VSTD::swap(this->__end_, __v.__end_);
    9. _VSTD::swap(this->__end_cap(), __v.__end_cap());
    10. __v.__first_ = __v.__begin_;
    11. __annotate_new(size());
    12. __invalidate_all_iterators();
    13. }

    在看下__alloc_traits::__construct_backward 这块 代码

    1. template <class _Ptr>
    2. _LIBCPP_INLINE_VISIBILITY
    3. static
    4. void
    5. __construct_backward(allocator_type& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2)
    6. {
    7. while (__end1 != __begin1)
    8. {
    9. construct(__a, _VSTD::__to_raw_pointer(__end2-1), _VSTD::move_if_noexcept(*--__end1));
    10. --__end2;
    11. }
    12. }

    代码看到这里,基本已经水落石出了, 我们看到上边有一个很关键的代码_VSTD::move_if_noexcept(*--__end1), 从字面意思也能看出来它是什么意思, 接着看下它的源码

    1. emplate <class _Tp>
    2. inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
    3. typename conditional
    4. <
    5. !is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value,
    6. const _Tp&,
    7. _Tp&&
    8. >::type
    9. move_if_noexcept(_Tp& __x) _NOEXCEPT
    10. {
    11. return _VSTD::move(__x);
    12. }

    这块代码就比较复杂了, move_if_noexcept 返回值使用了SFINA的技术,  conditional是一个条件判断语句, 如果它第一类型是true, 则返回const_TP&, 如果是false 则返回类型 _Tp&& , 那就看下!is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value 这个到底表达什么意思, 从标准库源代码is_nothrow_move_constructible<_Tp>::value 是判断_TP这个类型是否有不抛一场的移动构造方法, is_copy_constructible<_Tp>::value 并且拷贝构造方法, 

    源码看到这里大家心里就很清楚了, 到底咋回事!

  • 相关阅读:
    淘宝图片搜索API / item_search_img-按图搜索淘宝商品(拍立淘)/图片搜索API调用值说明
    vue3为什么用proxy替代object.defineProperty
    Problem E: 4*4矩阵填充
    C# Solidworks二次开发:枚举应用实战(第四讲)
    eclipse svn插件安装
    安徽工业大学计算机考研资料汇总
    linux开发板运行某一个脚本后报No such file or directory
    28-SpringBoot 异步任务、定时任务、邮件任务
    leetcode做题笔记191. 位1的个数
    6年Java开发,海投30份简历屡次被拒,该不该换个方向?
  • 原文地址:https://blog.csdn.net/c553110519/article/details/132629860