c++2.0中,一条经典的规范是:尽可能地为一个函数加上noexcept声明,意味着程序员向编译器保证该函数不会发射异常。
这条规范说的很对。那么,本文我们主要来探讨:
- 为什么给函数加上noexcept会优化其性能?
- noexcept的常见用法?
- 你可能会觉得自己也不确定这个函数是否会在运行时发射异常,那么到底什么时机应该为函数加上noexcept可以获得最佳优化?
这个原因从直观上理解应该是:既然开发者确保此函数不会发射异常,那么编译器也就没有必要为处理这个”可能“发生的异常添加一些事先预备好的目标代码,这在一定程度上减少了函数编译后生成的目标代码。
二、 noexcept的常见用法及注意事项c++11和c++98对阻止异常抛出的写法不一样:
void func()throw(){}//c++98,这种写法并不能完全享受到上面提到的那种优化
void func()noexcept{}//c++11
在c++11中,noexcept的用法如下:
void func()noexcept{}//1.
void func()noexcept(express){}//2.
第二种写法将根据表达式的真假来决断函数是否发射异常,noexcept等价于noexcept(true)。
需要注意的是,如果承诺了func函数是不会抛出异常的,那么必须保证func调用的其他函数也是不会抛出异常的,否则无法保证func的noexcept性质。因此,我们可以百分百确定一个函数不会发射异常的情况是比较少见的!需要了解的是,c++11为所有类的析构函数都加上了“隐式”noexcept声明。
另外,假设我们承诺的noexcept函数在运行时真的发射了异常会怎么样呢?(一定要注意,如果使用了catch子句捕获并处理了异常,那不算发射异常!)我在GNU下测试过,运行时还是会抛出异常然后程序崩溃。只是在编译生成目标代码时做了优化而已。
void func()noexcept{
int* a=nullptr;
*a=2;//抛出一个异常
}
bash输出:
zkcc@LAPTOP-OHBI7I8S:~/mytest$ g++ test_cast.cc -o test_cast && ./test_cast
Segmentation fault
三、为函数加上noexcept声明的最佳时机
直接给出建议:当你为一个类设计移动系列函数时,如果可以,最应该为其加上noexcept,以便此类在使用标准库容器时可以用移动 *** 作来代替拷贝。
咋回事呢?移动 *** 作加上noexcept与标准库容器的移动优化有啥关系呢?
以vector为例,很多标准库容器需要特定时机的扩容 *** 作:把元素从旧内存拷贝到新开辟的内存,再析构旧内存中的元素。
针对这种情况明显可以用移动 *** 作来优化之。(为什么移动 *** 作比拷贝 *** 作速度更快,什么时候会有明显的优化?这个问题我们之后也会讨论!)
但标准库的做法是:如果容器中类的移动 *** 作函数带有noexcept声明,则使用移动 *** 作来代替拷贝;如果没有noexcept声明,则用拷贝来完成扩容。
因此你会发现,将各种移动系列的函数设计为noexcept,会对标准库性能提升有巨大的帮助!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)