C++异常安全和RAII
# 异常处理时析构函数仍会执行。
// #define _GNU_SOURCE
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <string>
#include <iostream>
using namespace std;
class CTest {
public:
CTest() {
printf("CTest construct\n");
}
~CTest() {
printf("~CTest deconstruct\n");
}
};
int main() {
try {
{
CTest a;
throw 1;
printf("after throw\n");
}
}
catch(int t) {
printf("catch int t=%d\n", t);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
输出:
CTest construct
~CTest deconstruct
catch int t=1
1
2
3
2
3
可见 CTest a 仍然执行了析构函数,并不会因为throw异常而不执行析构。
# 异常的基本使用
参考 https://blog.csdn.net/ccc369639963/article/details/122905277 (opens new window)
在 C++ 中,我们使用 throw 关键字来显式地抛出异常,它的用法为:
throw exceptionData;
exceptionData 是“异常数据”的意思,它可以包含任意的信息,完全有程序员决定。exceptionData 可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型,请看下面的例子:
char str[] = "http://c.biancheng.net";
char *pstr = str;
class Base{public: int mm=123;};
Base obj;
throw 100; //int 类型
throw str; //数组类型
throw pstr; //指针类型
throw obj; //对象类型
try{
throw obj;
} catch (Base b){
printf("catch Base, b.mm=%d\n", b.mm);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
throw的对象直接catch用就行了,没有任何特殊之处。下面自定义一个 HpcException 类
// #define _GNU_SOURCE
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <string>
#include <iostream>
using namespace std;
// max msglen 4096;
class HpcException: std::exception {
public:
template<typename... Args>
explicit HpcException(const char* format, Args... args) throw() {
constexpr int msglen = 4096;
char buf[msglen];
std::snprintf(buf, msglen, format, args...);
what_.assign(buf);
}
const char* what() const throw()
{
return what_.c_str();
}
private:
std::string what_;
};
int main() {
try {
int aa = 5;
throw HpcException("some thing wrong: aa=%d", aa);
}
catch(HpcException e) {
printf("catch exception: %s\n", e.what());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 用RAII处理异常安全
参考: https://www.cnblogs.com/mavaL/articles/2515381.html (opens new window)
假设需要一段下面逻辑的代码:
void User::AddFriend(User& newFriend)
{
friends_.push_back(&newFriend);//操作1
try
{
pDB_->AddFriend(GetName(), newFriend.GetName()); //如果该语句异常,则撤销操作1
}
catch (...)
{
friends_.pop_back();//撤销操作1
throw;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
上面的操作很繁琐,而且有多个撤销操作时,代码会非常复杂。
此时可以通过ScopeGuard来实现,方便快捷,逻辑清晰:
void User::AddFriend(User& newFriend)
{
friends_.push_back(&newFriend);
ScopeGuard guard = MakeObjGuard(friends_, &UserCont::pop_back); //创建一个guard对象, 绑定撤销函数, guard析构时会调用该撤销函数
pDB_->AddFriend(GetName(), newFriend.GetName());
guard.Dismiss();//只有执行了Dismiss, guard在析构时才不会执行撤销函数
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
注意在实现ScopeGuard类时,调用撤销函数时要加上try catch(...),因为析构函数中不应该抛出异常。
编辑 (opens new window)
上次更新: 2023/05/07, 17:27:54