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

背景:

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

过程:

测试代码如下:

复制代码
#include <iostream>
#include <vector>
struct A
{
	A(){
		std::cout<<"A::A()"<<std::endl;
	}
	A(const A &a)
	{
		std::cout<<"A::A(const A&a)"<<std::endl;
	}
	A(A &&a) 
	{
		std::cout<<"A::A(A &&a)"<<std::endl;
	}
	A& operator=(const A&a) 
	{
		std::cout<<"operator=(const A&a)"<<std::endl;
		return *this;
	}
	A& operator = (A &&a)
	{
		std::cout<<"operator =(A&&a)"<<std::endl;
		return *this;
	}
};
int main()
{
	std::vector<A> vecA;
    A a;
    vecA.push_back(a);
    std::cout<<"1"<<std::endl;
    vecA.push_back(a);
    std::cout<<"2"<<std::endl;
    vecA.push_back(a);
    std::cout<<"3"<<std::endl;
    vecA.push_back(a);
    std::cout<<"4"<<std::endl;
	return 0;

}

执行结果如下:

复制代码
A::A()
A::A(const A&a)
1
A::A(const A&a)
A::A(const A&a)
2
A::A(const A&a)
A::A(const A&a)
A::A(const A&a)
3
A::A(const A&a)
4

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

复制代码
#include <iostream>
#include <vector>
struct A
{
	A(){
		std::cout<<"A::A()"<<std::endl;
	}
	A(const A &a)
	{
		std::cout<<"A::A(const A&a)"<<std::endl;
	}
	A(A &&a) noexcept
	{
		std::cout<<"A::A(A &&a)"<<std::endl;
	}
	A& operator=(const A&a) noexcept
	{
		std::cout<<"operator=(const A&a)"<<std::endl;
		return *this;
	}
	A& operator = (A &&a)
	{
		std::cout<<"operator =(A&&a)"<<std::endl;
		return *this;
	}
};
int main()
{
	std::vector<A> vecA;
	A a;
	vecA.push_back(a);
	std::cout<<"1"<<std::endl;
	vecA.push_back(a);
	std::cout<<"2"<<std::endl;
	vecA.push_back(a);
	std::cout<<"3"<<std::endl;
	vecA.push_back(a);
	std::cout<<"4"<<std::endl;
	return 0;

}

执行结果如下:

复制代码
A::A()
A::A(const A&a)
1
A::A(const A&a)
A::A(A &&a)
2
A::A(const A&a)
A::A(A &&a)
A::A(A &&a)
3
A::A(const A&a)
4

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

揭秘:

push_back 源码如下:

复制代码
template <class _Tp, class _Allocator>
inline _LIBCPP_INLINE_VISIBILITY
void
vector<_Tp, _Allocator>::push_back(const_reference __x)
{
    if (this->__end_ != this->__end_cap())
    {
        __RAII_IncreaseAnnotator __annotator(*this);
        __alloc_traits::construct(this->__alloc(),
                                  _VSTD::__to_raw_pointer(this->__end_), __x);
        __annotator.__done();
        ++this->__end_;
    }
    else
        __push_back_slow_path(__x);
}

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

复制代码
#ifndef _LIBCPP_CXX03_LANG
vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x)
#else
vector<_Tp, _Allocator>::__push_back_slow_path(_Up& __x)
#endif
{
    allocator_type& __a = this->__alloc();
    __split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
    // __v.push_back(_VSTD::forward<_Up>(__x));
    __alloc_traits::construct(__a, _VSTD::__to_raw_pointer(__v.__end_), _VSTD::forward<_Up>(__x));
    __v.__end_++;
    __swap_out_circular_buffer(__v);
}

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

复制代码
template <class _Tp, class _Allocator>
void
vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v)
{
    __annotate_delete();
    __alloc_traits::__construct_backward(this->__alloc(), this->__begin_, this->__end_, __v.__begin_);
    _VSTD::swap(this->__begin_, __v.__begin_);
    _VSTD::swap(this->__end_, __v.__end_);
    _VSTD::swap(this->__end_cap(), __v.__end_cap());
    __v.__first_ = __v.__begin_;
    __annotate_new(size());
    __invalidate_all_iterators();
}

在看下__alloc_traits::__construct_backward 这块 代码

复制代码
    template <class _Ptr>
    _LIBCPP_INLINE_VISIBILITY
    static
    void
    __construct_backward(allocator_type& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2)
    {
        while (__end1 != __begin1)
        {
            construct(__a, _VSTD::__to_raw_pointer(__end2-1), _VSTD::move_if_noexcept(*--__end1));
            --__end2;
        }
    }

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

复制代码
emplate <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11

typename conditional
<
    !is_nothrow_move_constructible<_Tp>::value && is_copy_constructible<_Tp>::value,
    const _Tp&,
    _Tp&&
>::type

move_if_noexcept(_Tp& __x) _NOEXCEPT
{
    return _VSTD::move(__x);
}

这块代码就比较复杂了, 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 并且拷贝构造方法,

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

相关推荐
宵时待雨2 小时前
优选算法专题9:哈希表
数据结构·算法·散列表
gwf2163 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
Iruoyaoxh3 小时前
栈和队列~
java·开发语言
noipp3 小时前
推荐题目:洛谷 P6231 [JSOI2013] 公交系统
c语言·数据结构·c++·算法·游戏·洛谷·luogu
Hrain-AI3 小时前
2026 企业 AI 编程智能体实战:Codex 与 Claude Code
开发语言·人工智能·kotlin
c238563 小时前
C/C++每日一练6
c语言·c++·算法
wdfk_prog3 小时前
嵌入式面试真题第 13 题:单硬件 Timer 下的高精度多路软件定时器架构设计
c语言·开发语言·面试·职场和发展·嵌入式
oort1233 小时前
吃上了自家的细糠,还挺丝滑,用起来手感还行,OortCloud发布新版AI编程平台,下载 OortCodex,Token多,免费薅
大数据·开发语言·人工智能·ai编程
念恒123064 小时前
网络基础
linux·网络·c++
AI小码4 小时前
把动作「画」给视频世界模型,跨本体双向推演,李飞飞参与
大数据·人工智能·算法·ai·大模型·音视频·编程