1、noexcept修饰符
-
C++98中使用throw()进行动态异常声明 void except_func() throw(int ,double){ … } 在except_func函数后定义了一个动态异常声明throw(int, double)。 该声明指出了except_func可能抛出的异常类型.但是此特性很少被使用,故被弃用 -
C++11使用noexcept取代throw()进行动态异常声明 语法上有两种形式 void except_func() noexcept {…} void except_func() noexcept(常量表达式) {…} 常量表达式会被转化为bool类型的值,该值为true表示不会抛出异常,即相当于不带表达式的形式。
2、noexcept作用
noexcept作用之一就是阻止异常的传播和扩散。
#include <iostream>
void thowexcept(){ throw 1;}
void NoBlockThrow(){ thowexcept();}
void BlockThrow() noexcept { thowexcept();}
int main(){
try{
thowexcept();
}
catch(...){
std::cout << "found throw" << std::endl;
}
try{
NoBlockThrow();
}
catch(...){
std::cout << " NoBlockThrow " << std::endl;
}
try{
BlockThrow();
}
catch(...){
std::cout << " BlockThrow "<< std::endl;
}
return 0;
}
虽然terminate()调用可能带来很多问题,比如无法保证析构函数正常调用,无法保证栈的自动释放等。但是很多时候暴力终止程序是最简单有效的办法。事实上,noexcept被广泛的,系统的用于c++11的标准库中,用于提高标准库的性能,以及满足一些阻止异常扩散的需求。
3、noexcept操作符
noexcept作为操作符一般用于模板: template < class T > void noexecpt_func() noexcept( noexcept( T() ) ) {…} noexecpt_func()是否为一个noexcept函数,取决于T()表达式是否会抛出异常。第二个exception就是一个noexcept操作符。
4、noexcept性能
因为在调用noexcept函数时不需要记录exception handler所以编译器可以生成更efficient的code(但实际编译器是否优化noexcept不一定,但理论上noexcept给了编译器更多优化的机会)。另外编译器在编译一个非noexcept的函数时有可能会生成很多冗余的代码,这些代码虽然只在出错的时候执行,但还是会对instruction cache造成影响,进而影响程序整体的performance。 下面是一段代码的noexcept(fasle)和noexcept的汇编代码比较
void foo();
struct Obj
{
~Obj();
};
void goo()
{
Obj obj;
foo();
}
4.1 noexcept(false)汇编代码
.file "testnoexcept.cpp"
.text
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.text
.LHOTB0:
.p2align 4,,15
.globl _Z3goov
.type _Z3goov, @function
_Z3goov:
.LFB0:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA0
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
subq $16, %rsp
.cfi_def_cfa_offset 32
.LEHB0:
call _Z3foov
.LEHE0:
leaq 15(%rsp), %rdi
call _ZN3ObjD1Ev
addq $16, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 16
popq %rbx
.cfi_def_cfa_offset 8
ret
.L3:
.cfi_restore_state
movq %rax, %rbx
jmp .L2
.globl __gxx_personality_v0
.section .gcc_except_table,"a",@progbits
.LLSDA0:
.byte 0xff
.byte 0xff
.byte 0x1
.uleb128 .LLSDACSE0-.LLSDACSB0
.LLSDACSB0:
.uleb128 .LEHB0-.LFB0
.uleb128 .LEHE0-.LEHB0
.uleb128 .L3-.LFB0
.uleb128 0
.LLSDACSE0:
.text
.cfi_endproc
.section .text.unlikely
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDAC0
.type _Z3goov.cold.0, @function
_Z3goov.cold.0:
.LFSB0:
.L2:
.cfi_def_cfa_offset 32
.cfi_offset 3, -16
leaq 15(%rsp), %rdi
call _ZN3ObjD1Ev
movq %rbx, %rdi
.LEHB1:
call _Unwind_Resume
.LEHE1:
.cfi_endproc
.LFE0:
.section .gcc_except_table
.LLSDAC0:
.byte 0xff
.byte 0xff
.byte 0x1
.uleb128 .LLSDACSEC0-.LLSDACSBC0
.LLSDACSBC0:
.uleb128 .LEHB1-.LCOLDB0
.uleb128 .LEHE1-.LEHB1
.uleb128 0
.uleb128 0
.LLSDACSEC0:
.section .text.unlikely
.text
.size _Z3goov, .-_Z3goov
.section .text.unlikely
.size _Z3goov.cold.0, .-_Z3goov.cold.0
.LCOLDE0:
.text
.LHOTE0:
.ident "GCC: (GNU) 8.4.1 20200928 (Red Hat 8.4.1-1)"
.section .note.GNU-stack,"",@progbits
4.2 noexcept汇编代码
.file "testnoexcept.cpp"
.text
.p2align 4,,15
.globl _Z3goov
.type _Z3goov, @function
_Z3goov:
.LFB0:
.cfi_startproc
subq $24, %rsp
.cfi_def_cfa_offset 32
call _Z3foov
leaq 15(%rsp), %rdi
call _ZN3ObjD1Ev
addq $24, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size _Z3goov, .-_Z3goov
.ident "GCC: (GNU) 8.4.1 20200928 (Red Hat 8.4.1-1)"
.section .note.GNU-stack,"",@progbits
5、noexcept安全性
《深入理解c++11》中是这么说的。我这里暂时还不知道怎么理解 请有知道的小伙伴帮忙留言
6、什么时候使用
- 现在编译器在好路径上异常没有影响,noexcept可能的作用是减小体积
- 推荐在构造、复制等常用操作标记noexcept,这样性能提升可能会比较大。例如vector不会使用你的类move操作,除非它被标记为noexcept(有的编译器能自动推导)
- destructor一定用noexcept
- 简单的leaf function,像是int,pointer这类的getter,setter用noexcept。因为不可能出错
|