throw && Exception
内存不足,抛出bad_alloc异常
logic_error中的length_error:长度错误
out_of_range:越界异常
invalid_argument:参数非法
exception和logic_error和runtime_error是定义在头文件<exception>中的
bad_alloc是定义在<new>中的,当使用new或new[]时若内存不足,则抛出std::bad_alloc异常
bad_cast是定义在<type_info>中的,若daynamic_cast对引用进行转换类型的话,若失败,则抛出std::bad_cast异常;若对指针进行转换类型的话,若失败,返回空指针
std::system_error是与操作系统进行交互的过程中出现的异常,比如std::thread
<chrono>是与时间相关的头文件,其中定义了std::nonexistent_local_time和std::ambiguous_local_time两个异常
std::vector<int> v;
v.at(0) = 42;//检查下标是否合法,不合法抛出std::out_of_range异常
v[0] = 42;//不检查下标,但是可能会导致segmentation fault
栈展开/栈的开解
假设在int* p = new int[n];中出现了bad_alloc异常,则会出现栈回退,一些局部变量会被销毁
①在operator new[]中抛出bad_alloc异常
②控制流返回到int* p = new int[n];语句
③x出栈,被销毁
④n出栈,被销毁
⑤控制流返回到main函数中的func(size);语句
⑥size出栈,被销毁
栈展开:消除局部变量,退回到上个函数,再销毁局部变量,退回…
只针对被catch的异常才会出现栈展开
try catch
#include <iostream>
#include <exception>
void func(int n) {
int x = 42;
int* p = new int[n];
}
int main() {
try
{
int size = 100;
func(size);
}
catch (const std::bad_alloc& e)//异常传递要引用传递,尽量不拷贝
{
//处理异常
}
}
栈展开时退到了try发现异常会被处理,接着查看下面的catch是否有异常对应,然后处理异常
继承于exception的异常都有一个what函数,用于输出语句
#include <iostream>
#include <exception>
void func() {
throw std::runtime_error("I love u");
}
int main() {
try
{
func();
}
catch (const std::runtime_error& e)
{
std::cout << e.what() << std::endl;//输出“I love u”
}
return 0;
}
void func(const std::vector<int> &v) {
try
{
int i = 42;
std::vector<int> copy = v;//可能会出现std::bad_alloc异常
int x = copy.at(100);//可能会抛出std::out_of_range异常
g(x);
}
catch (const std::bad_alloc& ba)
{
}
catch (const std::out_of_range& oor) {
}
catch (...) {
throw;//单写一个throw是把接受的异常再抛出去
}
//后面的代码
}
若int x = copy.at(100);抛出out_of_range异常,程序进行倒退,销毁copy,i,然后到达try,找catch中是否有匹配的异常;catch (…)可匹配任何抛出的东西,不一定是异常
out_of_range在catch (const std::out_of_range& oor)处匹配并进行了处理,然后控制流可以执行后面的代码
若类中的构造函数的初始化出现异常,则在函数外使用try catch,try中包含了m_size(n), m_data(new T[n]{})等参数初始化操作,也包含了{}函数体部分
template <class T>
class Array {
public:
Array(std::size_t n) try : m_size(n), m_data(new T[n]{}) {}
catch (const std::bad_alloc& e) {
std::cerr << "no enough memory" << std::endl;
throw;
}
private:
int m_size;
int arr[m_size];
};
自定义异常类
class WrongAnswer : public std::logic_error {
public:
WrongAnswer(std::size_t line_number) : std::logic_error("Wrong answer on line" + std::to_string(line_number)) {};
//自定义一个异常类,继承自logical_error,输出出现异常的行数
};
#define assert(X) {
if (!(X)) throw WrongAnswer(__LINE__);//__LINE__是一个宏,返回行号
};
int main() {
assert((2 + 1) == 6);
return 0;
}
异常安全
异常安全指的是满足一下三种中的任意一个即可:
1.不抛出保证:即函数不抛出异常
2.强异常安全保证:函数执行过程中,异常被抛出之后,能够让程序回退到调用异常函数之前的状态
3.弱异常安全保证:函数执行中,异常被抛出,很难去回退到调用异常函数之前的状态,但是可以把程序设置成有效的状态
异常说明符
在C++11中,可以在函数名后面抛出可能的异常:
void *operator(std::size_t size) throw(std::bad_alloc) {};
如果声明此函数不抛出异常:
void *operator(std::size_t size) noexcept {};
函数不抛出异常noexcept可使函数效率更高
以拷贝控制为例
1.
class Array {
int *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) {
if (this != &other) {//检查是否是自我拷贝
delete[] m_data;
m_data = new int[other.m_size];//此处可能会抛出异常
std::copy(other.m_data, other.m_data + other.m_size, m_data);
m_size = other.m_size;
}
return *this;
}
};
m_data = new int[other.m_size];会抛出std::bad_alloc异常,然后进行栈展开,对原来进行的操作进行撤销,包括局部变量销毁,但是delete掉的m_data找不回来了,但是m_size还没有改。因此此类不是异常安全的
改为:
class Array {
int *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) {
if (this != &other) {//检查是否是自我拷贝
auto new_data = new int[other.m_size];//此处可能会抛出异常
std::copy(other.m_data, other.m_data + other.m_size, m_data);
delete[] m_data;
m_data = new_data;
m_size = other.m_size;
}
return *this;
}
};
具备强异常安全保证
2.
以下代码是强异常安全保证
class Array {
int *m_data;
std::size_t m_size;
public:
void swap(Array &other) noexcept {
using std::swap;
swap(m_size, other.m_size);
swap(m_data, other.m_data);
}
Array &operator=(const Array &other) {
Array(other).swap(*this);//Array(other)中隐藏了new,会抛出异常
return *this;
}
};
3.
template <class T>
class Array {
T *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) : m_data(new T[other.m_size]), m_size(other.m_size) {//new会抛出异常
std::copy(other.m_data, other.m_data + other.m_size, m_data);//std::copy也会抛出异常,因为现在这个T可能拷贝会出现异常,比如string就需要分配内存
return *this;
}
};
new会抛出异常的异常没关系,因为是一开始出现的,进行栈展开时没影响
但是std::copy的异常会出现问题,因为回退的时候m_size和m_data需要进行析构,但是m_data中有new了一块内存,会出现内存泄漏,所以需要try-catch一下,比如:
template <class T>
class Array {
T *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) : m_data(new T[other.m_size]), m_size(other.m_size) {//new会抛出异常
try
{
std::copy(other.m_data, other.m_data + other.m_size, m_data);//std::copy也会抛出异常,因为现在这个T可能拷贝会出现异常,比如string就需要分配内存
}
catch (...)
{
delete[] m_data;
throw;
}
return *this;
}
};
从内存的角度看,这时就是强异常安全的
从数据的更改角度看,这是若异常安全的
4.
template <class T>
class Array {
T *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) {
if (this != &other) {
auto new_data = new T[other.m_size];//此处可能会抛出异常
std::copy(other.m_data, other.m_data + other.m_size, m_data);//copy也会抛出异常
delete[] m_data;
m_data = new_data;
m_size = other.m_size;
}
return *this;
}
};
此代码中copy出现的异常不安全,因为会出现new_data刚new的内存出现泄露,所以需要try-catch一下,即
template <class T>
class Array {
T *m_data;
std::size_t m_size;
public:
Array &operator=(const Array &other) {
if (this != &other) {
auto new_data = new T[other.m_size];//此处可能会抛出异常
try {
std::copy(other.m_data, other.m_data + other.m_size, m_data);//copy也会抛出异常
}
catch (...) {
delete[] new_data;
throw;
}
delete[] m_data;
m_data = new_data;
m_size = other.m_size;
}
return *this;
}
};
5.
template <class T>
class Array {
T *m_data;
std::size_t m_size;
public:
void swap(Array &other) noexcept {
using std::swap;
swap(m_size, other.m_size);
swap(m_data, other.m_data);
}
Array &operator=(const Array &other) {
Array(other).swap(*this);//Array(other)中隐藏了new,会抛出异常
return *this;
}
};
这是强异常安全的