You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
voidfoo() noexcept; // a function specified as will never throwvoidfoo2() noexcept(true); // same as foovoidbar(); // a function might throw exceptionvoidbar2() noexcept(false); // same as bar
c++11提供了关键字
noexcept
,用来指明某个函数无法——或不打算——抛出异常:所以我们需要了解以下两点:
noexcept
有什么优点,例如性能、可读性等等。noexcept
。noexcept优点
我们先从std::vector入手来看一下第一点。
我们知道,vector有自己的capacity,当我们调用
push_back
但是vector容量满时,vector会申请一片更大的空间给新容器,将容器内原有的元素copy到新容器内:但是如果在扩容元素时出现异常怎么办?
这种扩容方式比较完美,有异常时也会保持上游调用
push_back
时原有的状态。但是为什么说比较完美,因为这里扩容还是copy的,当vector内是一个类且持有资源较多时,这会很耗时。所以c++11推出了一个新特性:
move
,它会将资源从旧元素中“偷”给新元素(对move不熟悉的同学可以自己查下资料,这里不展开说了)。应用到vector扩容的场景中:当vector中的元素的移动拷贝构造函数是noexcept
时,vector就不会使用copy方式,而是使用move方式将旧容器的元素放到新容器中:利用
move
的交换类资源所有权的特性,使用vector扩容效率大大提高,但是当发生异常时怎么办:原有容器的状态已经被破坏,有部分元素的资源已经被偷走。若要恢复会极大增加代码的复杂性和不可预测性。所以只有当vector中元素的
move constructor
是noexcept
时,vector扩容才会采取move方式来提高性能。刚才总结了利用
noexcept
如何提高vector扩容。实际上,noexcept
还大量应用在swap
函数和move assignment
中,原理都是一样的。noexcept使用场景
上面提到了
noexcept
可以使用的场景:很多人的第一念头可能是:我的函数现在看起来明显不会抛异常,又说声明
noexcept
编译器可以生成更高效的代码,那能加就加呗。但是事实是这样吗?这个问题想要讨论清楚,我们首先需要知道以下几点:
noexcept
一致性检查,例如下述代码是合法的:noexcept
的函数抛出异常时,程序会被终止并调用std::terminate();所以在我们的代码内部调用复杂,链路较长,且随时有可能加入新feature时,过早给函数加上
noexcept
可能不是一个好的选择,因为noexcept
一旦加上,后续再去掉也会变得困难 : 调用方有可能看到你的函数声明为noexcept,调用方也会声明为noexcept
。但是当你把函数的noexcept
去掉却没有修改调用方的代码时,当异常抛出到调用方会导致程序终止。目前主流的观点是:
throw()
noexcept
。除了上面的要加的情况,其余的函数不要加
noexcept
就可以。最后我们看一下vector如何实现利用
noexcept move constructor
扩容以及move constructor
是否声明noexcept
对扩容的性能影响。如何实现利用
noexcept move constructor
扩容这里就不贴大段的代码了,每个平台的实现可能都不一样,我们只关注vector是怎么判断调用
copy constructor
还是move constructor
的。其中利用到的核心技术有:
核心代码:
这里用
type trait
和iterator trait
联合判断:假如元素有noexcept move constructor
,那么is_nothrow_move_constructible=1
=>__move_if_noexcept_cond=0
=>__make_move_if_noexcept_iterator
返回一个move iterator
。这里move iterator
迭代器适配器也是一个c++11新特性,用来将任何对底层元素的处理转换为一个move操作,例如:然后上游利用生成的
move iterator
进行循环元素move:其中
_Construct
就是实际copy(或者move)元素的函数。这里很关键的一点是:对move iterator进行解引用操作,返回的是一个右值引用。,这也就保证了,当__first
类型是move iterator
时,用_T1(std::forward<_Args>(__args)...
进行“完美转发”才调用_T1
类型的move constructor
,生成的新对象被放到新vector的__p
地址中。总结一下过程就是:
type trait
和iterator trait
生成指向旧容器的normal iterator
或者move iterator
move iterator
,那么解引用会返回右值引用,会调用元素的move constructor
,否则调用copy constructor
。大家可以用下面这段简单的代码在自己的平台打断点调试一下:
noexcept move constructor
对性能的影响这篇文章C++ NOEXCEPT AND MOVE CONSTRUCTORS EFFECT ON PERFORMANCE IN STL CONTAINERS介绍了noexcept move constructor对耗时以及内存的影响,这里不重复赘述了,感兴趣的可以自己试一下。
参考资料:
朋友们可以关注下我的公众号,获得最及时的更新:
The text was updated successfully, but these errors were encountered: