-
总览
-
新机制
-
对noexcept 修饰的函数进行优化. -
noexcept 可以传导和查询.
-
异常
-
用异常代替if else . -
将if else 统一到一个地方处理.
-
异常
-
必须处理的错误,直接抛出异常. -
不需要处理的错误.
-
noexcept 函数
-
任意时刻调用都不会出错. -
noexcept 可以是非noexcept 组成.
-
主流函数
-
大部分都是可能抛出异常的. -
不一定非要追求noexcept ,不要用return code 代替异常.
-
错误检测
-
C++ 的异常
-
C++98 异常
-
可以通过throw(...) 枚举可能抛出的异常. 如果抛出了预期外的则终止程序. -
但是统一性的问题. 使用者很可能会对使用的函数将要throw 的异常比较关注.
-
C++98 一致性
-
使用程序的开发者对函数进行了修改,抛出的异常变化了. -
那么就需要改动throw(...) 里面的枚举,那么就会导致一些列相关的代码也需要修改. -
带来的问题,编译器也不会提醒,就会带来一系列的问题. -
这种设计是不好的,但是有些场景又是必须的.
-
C++11
-
只有两种状态: 肯定不会 和 可能会 . -
noexcept 或noexcept(true) 修饰的肯定不会. 其他则可能会.
-
C++11 和C++98 的差异
-
C++11 的noexcept 修饰,即肯定不会抛异常的函数会进行优化. -
C++98 则会尝试优化,但是还是会保留一些额外的信息. -
C++98 不抛出异常:throw() ,C++11 则是noexcept,noexcept(true) . -
C++98 出现期望之外的异常执行析构. C++11 遇到期望之外的会立即崩溃,不会清理资源.被优化掉了.
-
接口设计
-
在设计接口的时候就应该明确,是否可能会抛出异常. -
而且接口需要稳定,遵循开闭原则.
-
函数是否异常的影响
-
重要性
-
和函数的const 修饰一样. -
但是noexcept 不参与重载修饰.
-
C++98 和C++11 差异案例
[root@localhost test]# g++ test.cpp -std=c++98
[root@localhost test]# cat test.cpp
#include <iostream>
class T {
public:
T() {std::cout << __FUNCTION__ << std::endl;}
~T() {std::cout << __FUNCTION__ << std::endl;}
};
void show() throw()
{
T t;
throw 1;
}
int main() {
show();
}
[root@localhost test]# ./a.out
T
~T
terminate called after throwing an instance of 'int'
Aborted
-
C++11 不会,直接崩溃.栈回指只会发生在程序结束.编译器直接把释放的代码给优化掉了.不需要.因为已经保证肯定不会抛出异常.
[root@localhost test]# g++ test.cpp -std=c++11
[root@localhost test]# cat test.cpp
#include <iostream>
class T {
public:
T() {std::cout << __FUNCTION__ << std::endl;}
~T() {std::cout << __FUNCTION__ << std::endl;}
};
void show() noexcept
{
T t;
throw 1;
}
int main() {
show();
}
[root@localhost test]# ./a.out
T
terminate called after throwing an instance of 'int'
Aborted
-
编译器优化
-
void func() noexcept :全力优化; void func() throw() :简单优化; void func() :简单优化;
-
小结
-
就noexcept 函数会被编译器优化这一点,开发者就应该对肯定不会抛出异常的函数加上这个修饰.
-
noexcept 场景分析
-
std::vector 添加元素.
-
正确性问题
-
如果拷贝的时候出现异常.数据如何处理.copy 中发生异常,原来的数据还是完整的. -
拷贝失败,捕获还是什么? -
C++11 采用move 的方式优化,但是问题是,move 到一半,发生异常,原来的数据被修改,也无法回滚.
-
C++11 做法
-
拷贝根据类型的拷贝构造是否会抛出异常,选择使用元素的拷贝构造还是移动构造.
-
案例
#include <iostream>
class T {
public:
T() {std::cout << this << std::endl;}
T(const T& t) noexcept {std::cout << "copy" << this << std::endl;}
T(T&& t) noexcept {std::cout << "move" << this << std::endl;}
~T() {std::cout << this << std::endl;}
};
void show(T t) noexcept(noexcept(T(std::move(t)))){
if(noexcept(T(std::move(t)))) {
std::cout << "noexcept t " << &t << std::endl;
T d(std::move(t));
std::cout << "noexcept d " << &d << std::endl;
} else {
std::cout << "except t " << &t << std::endl;
T d(t);
std::cout << "except d " << &d << std::endl;
}
}
int main() {
T t;
show(t);
}
0x61fe1e
copy 0x61fe1f
noexcept t 0x61fe1f
move 0x61fddf
noexcept d 0x61fddf
0x61fddf
0x61fe1f
0x61fe1e
[Finished in 485ms]
#include <iostream>
class T {
public:
T() {std::cout << this << std::endl;}
T(const T& t) noexcept {std::cout << "copy" << this << std::endl;}
T(T&& t) {std::cout << this << "move" << std::endl;}
~T() {std::cout << this << std::endl;}
};
void show(T t) noexcept(noexcept(T(std::move(t)))){
if(noexcept(T(std::move(t)))) {
std::cout << "noexcept t " << &t << std::endl;
T d(std::move(t));
std::cout << "noexcept d " << &d << std::endl;
} else {
std::cout << "except t " << &t << std::endl;
T d(t);
std::cout << "except d " << &d << std::endl;
}
}
int main() {
T t;
show(t);
}
0x61fd3e
copy 0x61fd3f
except t 0x61fd3f
copy 0x61fbae
except d 0x61fbae
0x61fbae
0x61fd3f
0x61fd3e
[Finished in 472ms]
-
建议
-
拷贝,移动,swap 这些函数实现成noexcept . -
拷贝和移动,析构默认是noexcept ,但是默认生成的行为不符合预期.
-
扩展
#include <iostream>
void show(int a) noexcept(noexcept(a) && noexcept(a)){
}
int main() {
int a;
show(a);
}
-
优化和noexcept
-
回顾
-
前面的优化方案令人心动.是不是声明了程序就可以跑得更快了。 -
但是正确性和开发效率,代码整洁等等都是需要考量的。
-
设计
-
先声明为noexcept ,后期再改回来? -
随意改动会影响到其他使用到这个函数的人,不建议随意修改,自己使用另说.
-
改成noexcept
-
修改成noexcept ,但是使用了可能抛异常的,就会导致异常无法传递,然后崩溃.
-
主流
-
都是 异常中立函数. 异常中立函数就是没有声明任何异常,也不是noexcept . -
这类可以传递使用的代码抛出的异常. 然后不做处理就直接放行.
-
默认noexcept
-
肯定不会抛异常的函数
-
异常和返回值
-
将异常捕获改成返回值的方式,然后把函数改成noexcept ,这种就有点本末倒置了.
-
noexcept 和开发
-
noexcept
-
异常的好处
-
使用exception 替代返回值的方式可以提高开发效率. -
可以减少因为返回值带来的if else 判断分支. -
可以避免if else 带来的函数膨胀. -
可以简化代码结构.将if else 放到另外的地方处理.
-
返回值的弊端
-
虽然noexcept 可能会很快,但是代价就是导致代码复杂. -
代码结构混乱,分支变多,维护麻烦. -
带来的一系列问题和开销可能还不如用异常来处理.
-
提供功能
-
功能提供者不应该检测数据的有效性. -
应该由调用者确保数据的合法性. -
外观模式的主要核心就是保障合法. -
出现问题则就应该直接抛出异常,让调用者自行处理.
-
函数设计
-
wide contracts | narrow contracts
-
wide 就是不检测入参无限制,程序任意时刻可运行. -
narrow 就是入参有限制,错误参数程序会出错.
-
wide 常见就是
#include <iostream>
void show(int a){
}
int main() {
int a;
show(a);
}
-
narrow
#include <iostream>
void show(int a){
int c = 10 / a;
}
int main() {
int a;
show(a);
}
-
a 明显不能等于0 . 但是不会检查,由调用者保证有效.
-
noexcept
-
narrow
-
没有责任检测合法性, 会带来很多分支和性能的开销. -
应该由调用者检查合法性,有的时候甚至不需要检测. -
检查不强制,函数实现者也可以在函数内检测,不反对但是不推荐.
-
noexcept 不一定非要noexcept 实现
-
wide
-
wide 和narrow 区分
-
wide 一般用noexcept ,narrow 则没有.
-
noexcept 调用非noexcept
#include<iostream>
void setup();
void cleanup();
void doWork() noexcept
{
setup();
cleanup();
}
int main() {
doWork();
}
void setup() {
printf("%s\n",__FUNCTION__);
}
void cleanup() {
printf("%s\n",__FUNCTION__);
}
-
调用非noexcept ,可能是C 函数,没有noexcept . -
或者就是C++98 的,没有noexcept .
-
总结
-
noexcept 对使用者很重要. -
noexcept 的效率高,但是不要可以强求. -
noexcept 在move,swap 析构这些场景非常适合. -
主流的是抛异常函数而非noexcept .
|