前言
在C++编程中,异常处理是一种重要的错误处理机制,它能够在程序遇到错误时,优雅的处理这些错误,而不是让程序崩溃。在C++中通过try、catch、throw关键字来实现异常处理。并且标准库也提供了std::exception类以及一些派生来来捕获处理异常。在C++中,异常可以是任何类型的对象,C++定义了一些基本的异常类,包括std::exception、std::bad_alloc、std::bad_cast等。并且我们也可以通过自己定义一个类然后继承公共基类exception,使用其中what方法来实现自己的异常类。
C语言的异常机制是使用函数的返回值,比如0代表正常-1代表错误,但是函数的返回值是可以忽略的,而C++的异常却无法忽略,不捕获会导致程序异常退出。
语法
#include<iostream>
#include<exception>
using namespace std;
int main()
{
try
{
throw "123";
}
catch (const char*p)
{
cout << p << endl;
}
catch (exception&o)
{
cout << o.what() << endl;
}
catch(...)
{
}
return 0;
}
使用try{}将可能抛出异常的代码覆盖,通过throw去抛出异常,比如内存不足了,返回值错误,所要读取的文件不存在之类,然后使用控制块catch去捕捉错误,从上到下依次比对抛出异常类型是否与catch的类型相匹配,只有匹配的类型才可捕获,catch(…)可以捕获任意异常对象。
栈解旋
当我们从try块中抛出一个异常,到异常被抛出前的所有构造的对象都会被析构,且析构顺序与构造顺序相反,这一过程称为栈解旋。
#include<iostream>
#include<exception>
using namespace std;
class er :public exception
{
public:
er(string ms):exception(ms.c_str()) { }
~er() {}
};
void fun1();
void fun2();
void fun3();
void fun4();
class A
{
public:
A(string str) { this->str = str; }
~A() { cout << str << endl; }
string str;
};
void fun1()
{
A a("~fun1");
fun2();
cout << "fun1" << endl;
}
void fun2()
{
A a("~fun2");
fun3();
cout << "fun2" << endl;
}
void fun3()
{
A a("~fun3");
fun4();
cout << "fun3" << endl;
}
void fun4()
{
A a("~fun4");
throw bad_alloc();
cout << "fun4" << endl;
}
int main()
{
try
{
fun1();
}
catch (const int &o)
{
cout << "int" << endl;
}
catch (const exception& o)
{
cout << o.what() << endl;
}
cout << "finnal" << endl;
}
看这段代码从fun4抛出一个std::bad_alloc的异常对象,在main函数中进行捕获,此时会发生什么呢?
可以看到,抛出异常之后确实发生栈解旋,这样已经构造完成的对象就会被析构,不会造成内存泄漏。
构造函数和析构函数发生异常
我们从上面知道如果是已经构造完毕的对象那么由于栈解旋会成功被析构掉,但是如果是正在构造或者析构的对象会发生什么呢?是否可以在构造函数或者析构函数中抛出异常呢?
在构造函数中抛出异常:
#include<iostream>
#include<vector>
#include<exception>
#include<memory>
using namespace std;
class base
{
public:
base() { cout << "base构造函数" << endl; }
~base() { cout << "base析构函数" << endl; }
int a = 1;
};
class A
{
public:
A():p(new base)
{
throw 1;
}
~A()
{
delete p;
cout << "A析构函数" << endl;
}
base* p;
};
int main()
{
try
{
A a;
}
catch (const int&)
{
cout << "捕获异常" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
构造A a对象时抛出异常,进行捕获,那此时出了try{}是否会调用a的析构函数呢?
可以看到并没有调用析构函数,也就是说构造函数不完成对象没有构建完毕是不会调用析构函数的,此时发生了内存泄漏!!!如何解决呢?
智能指针
我们可以使用智能指针去管理申请的堆区内存,此时智能指针会随着离开作用域而析构,堆区资源也就被释放了。
#include<iostream>
#include<vector>
#include<exception>
#include<memory>
using namespace std;
class base
{
public:
base() { cout << "base构造函数" << endl; }
~base() { cout << "base析构函数" << endl; }
int a = 1;
};
class A
{
public:
A():p(new base)
{
throw 1;
}
~A()
{
cout << "A析构函数" << endl;
}
unique_ptr<base> p;
};
int main()
{
try
{
A a;
}
catch (const int&)
{
cout << "捕获异常" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
查看结果:
使用成员函数去申请内存
当一个对象涉及很复杂的初始化操作时,我们可以在构造函数中只进行简单的构造部分,然后另外使用一个Init()函数去完成复杂的初始化行为,这样即使Init()函数抛出异常,也会正常使用析构函数。
#include<iostream>
#include<vector>
#include<exception>
#include<memory>
using namespace std;
class base
{
public:
base() { cout << "base构造函数" << endl; }
~base() { cout << "base析构函数" << endl; }
int a = 1;
};
class A
{
public:
A():p(new base)
{
}
~A()
{
delete p;
cout << "A析构函数" << endl;
}
void test()
{
throw 1;
}
base* p;
};
int main()
{
try
{
A a;
a.test();
}
catch (const int&)
{
cout << "捕获异常" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
在构造函数中捕获异常
我们可以在构造函数中直接捕获异常,然后进行堆区资源的释放处理,但是一定要在构造函数中继续将异常抛出来,不然就会导致后续代码访问了类中已经被释放的资源而导致程序崩溃。
#include<iostream>
#include<vector>
#include<exception>
#include<memory>
using namespace std;
class base
{
public:
base() { cout << "base构造函数" << endl; }
~base() { cout << "base析构函数" << endl; }
int a = 1;
};
class A
{
public:
A()
{
try
{
p = new base;
p1 = new base;
throw 1;
p2 = new base;
}
catch (const int&)
{
if (p)
{
delete p;
p = nullptr;
}
if (p1)
{
delete p1;
p1 = nullptr;
}
if (p2)
{
delete p2;
p2 = nullptr;
}
throw 1;
}
}
~A()
{
cout << "A析构函数" << endl;
}
base* p{nullptr};
base* p1{nullptr};
base* p2{nullptr};
};
int main()
{
try
{
A a;
//如果不在A的构造函数中抛出异常,那么就会继续执行到下面代码程序将直接崩溃
//a.p->a = 2;
}
catch (const int&)
{
cout << "1111" << endl;
}
catch(const exception&e)
{
cout << e.what() << endl;
}
return 0;
}
在析构函数中抛出异常也是同理。
一些细节
自定义异常类
还有一个细节,可以发现我们在try块中构建了临时异常对象,但是居然可以在catch中用引用捕获,这是因为throw抛出的异常对象是会被放到异常存储区中,直到被catch看到。
类型必须完全匹配
catch的参数类型必须与throw的异常类型完全一致。
catch (...)
捕获所有未被处理的异常(通常用于兜底)。
noexcept
void safeFunction() noexcept { // 承诺不抛出异常
try {
// 可能抛出异常的代码
}
catch (…) {
// 处理所有异常,避免异常传播到 noexcept 函数外
std::terminate(); // 默认行为:终止程序
}
}
在初始化列表中try
#include<iostream>
#include<exception>
using namespace std;
class MyClass {
public:
MyClass(int key, int value) try :key(new int(key)), value(new int(value))
{
// 假设可能抛出异常
if (value < 0 || key < 0) throw std::invalid_argument("值不能为负");
}
catch(exception&e)
{
if (this->key)delete this->key;
if (this->value)delete this->value;
std::cerr << "构造失败: " << e.what() << std::endl;
throw;
}
private:
int* key;
int* value;
};