关于 “noexcept” 关键字的使用,C/C++ 开发人员用的不多,常见的用法是在写 FCL(框架类库)及 BCL(基础类库)上用的多一点。
在 C/C++ 11 以前大家是用 “throw()” 来声明的,当然进入到 C/C++ 11 之后人们应首选采用关键字 “noexcept” 来描述函数潜在的异常抛出。
noexcept 关键字的作用跟 throw() 的作用差不多,就是标记声明当前函数可能存在 “抛出异常” 的行为。
参考:
noexcept(true) 函数不会抛出异常
noexcept(false) 函数会抛出异常(函数未声明则默认为该规则)
throw() 函数不会抛出异常
throw(int) 函数会抛出 int 类型的异常
throw(int, std::string) 函数会抛出 int、std::string 类型的异常
例如:
其它开发人员如何知道调用的某个 C/C++ 函数内部实现,是否存在异常的情况? 如果人们不显示为函数标记一下,那么该函数发生异常时,调用方没有对异常(结构化异常)进行处理,那么导致的结果就是程式崩溃,对于服务器这类追求 7*24、30*24、365*24 小时不出现任何故障的程序而言,毫无疑问是潜在巨大的风险挑战。
这是早前 throw() 关键字的作用,但到达 C++ 11 时代,开发人员采用 noexcept 关键字可以相对更灵活的控制,某个函数是否编译关于结构化异常处理的代码。
例如:
当人们为函数声明 noexcept(true) = noexcept 时,则该函数内不允许抛出异常且 C/C++ 编译器不处理关于结构化异常相关的编译器实现代码,可以相对的优化程序编译后的总体大小,但不意味着 noexcept(false) 声明的函数无法调用 “需要抛出异常的函数”,这两者之间不冲突,noexcept(false) 仅仅只是意味着,该函数自身实现不会抛出异常。
而且一个好的编码建议,C/C++ 开发人员应当严格遵循 C/C++ 结构化异常处理的规范,不需要抛出异常的函数都必须明确声明,抛出异常的函数都应只抛出派生在 std::exception 下的类型,而不是各种数值结构体类型都来了,这不仅仅是为了减少 C/C++ 编译后程序的体积,更是代码可读性及执行效能的一种优化。
throw(...) 关键字声明在未来的 C/C++ 语言标准中,或被标准委员会移除【Obsolete】,所以人们应当在确保工程边界的情况下有限度的使用该关键字用于 C/C++ 结构化异常处理。
以VC++为例子:
我们知道 C/C++ 结构化异常的实现是通过 “串联数组链表” 实现的,该链表根由操作系统提供,当函数发生异常时(CPU发出中断信号)操作系统会把当前的EIP位置修正到结构化异常处理器链表上面,是一个迭代的结构从链顶部逐个查找匹配的异常处理器(Exception Handler)而 C/C++ 编译器实现的对于某个具体类型感兴趣 when case 是由 C/C++ 编译器结合类型系统来实现的。
Linux 平台上面对于异常的处理是通过操作系统内核发出的异常信号来处理的,当发生异常就会设置当前EIP切换到信号处理器(signal handler)上面,但 Linux 平台C/C++程序的异常处理并非是基于信号机制,而是编译器自行实现的一套,故而该异常处理机制存在相对明确的捕获边界。
下面在一个简单函数上声明 noexcept 与不声明会不会有质的区别!
代码:
int add(int x, int y) { return x+y; }
int add_noexcept(int x, int y) noexcept { return x+y; }
汇编:
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add(int,int) PROC ; add
push ebp
mov ebp, esp
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
pop ebp
ret 0
int add(int,int) ENDP ; add
_x$ = 8 ; size = 4
_y$ = 12 ; size = 4
int add_noexcept(int,int) PROC ; add_noexcept
push ebp
mov ebp, esp
mov eax, DWORD PTR _x$[ebp]
add eax, DWORD PTR _y$[ebp]
pop ebp
ret 0
int add_noexcept(int,int) ENDP ; add_noexcept
上述两个编译的汇编代码从结果上来说是没有区别的,这是因为两个 add 函数内部实现并不涉及结构化异常的一些处理。