c++异常详解
概念介绍
异常是一种处理程序错误的机制。用于:
- 将错误检测与错误处理分离
- 确保资源正确释放
- 允许错误处理代码集中处理
说到异常就离不开try/catch/noexcept/throw这个几个关键字。它们的作用如下:
- try/catch:这两个关键字成对使用,只有用这两个关键字包含的代码抛出的错误才能被捕获。catach后的用于错误处理,try部分的代码用于错误检测。
- throw:抛出异常的关键字。
- noexcept:c++11引入的新的关键字,通过该关键字可以显式的告诉编译器某个函数是否可能抛出异常。
基本的使用方法如下:
// 基本语法
try {
// 可能抛出异常的代码
throw MyException("error message");
} catch (const MyException& e) {
// 处理特定类型异常
} catch (...) {
// 处理任何异常
}
头文件依赖:#include <stdexcept>
异常的种类
c++允许抛出任意类型的异常,例如整型,字符串,枚举等,但是c++最推荐的还是抛出继承自std::exception的异常类对象。另外c++还存在一些标准的异常类,即c++已经封装好的异常类。
标准异常类
#include <stdexcept>
#include <string>
void function() {
// 1.1 标准异常类
throw std::runtime_error("Runtime error occurred"); //运行时错误
throw std::invalid_argument("Invalid argument"); //无效参数
throw std::out_of_range("Index out of range"); //超出访问范围
throw std::logic_error("Logic error"); //逻辑错误
// 1.2 bad_alloc(内存分配失败)
throw std::bad_alloc();
// 1.3 bad_cast(类型转换失败)
throw std::bad_cast();
}
自定义异常类
// 2.1 基本自定义异常
class DatabaseException : public std::exception {
public:
DatabaseException(const std::string& message)
: message_(message) {}
const char* what() const noexcept override {
return message_.c_str();
}
private:
std::string message_;
};
// 2.2 带错误代码的异常
class NetworkException : public std::exception {
public:
NetworkException(int errorCode, const std::string& message)
: errorCode_(errorCode), message_(message) {}
const char* what() const noexcept override {
return message_.c_str();
}
int getErrorCode() const { return errorCode_; }
private:
int errorCode_;
std::string message_;
};
void connectToDatabase() {
throw DatabaseException("Failed to connect to database");
throw NetworkException(404, "Server not found");
}
基本数据类型(不推荐)
void riskyFunction() {
// 3.1 整数
throw 42; // 不推荐
// 3.2 字符串字面量
throw "Error occurred"; // 不推荐
// 3.3 字符串对象
throw std::string("Error"); // 不推荐
// 3.4 布尔值
throw false; // 不推荐
}
// 捕获基本类型的异常
void catchBasicTypes() {
try {
riskyFunction();
} catch (int e) {
std::cout << "Caught integer: " << e << std::endl;
} catch (const char* e) {
std::cout << "Caught string literal: " << e << std::endl;
} catch (const std::string& e) {
std::cout << "Caught string object: " << e << std::endl;
}
}
枚举类型(某些场景下可以实现)
// 4.1 普通枚举
enum ErrorCode {
FILE_NOT_FOUND,
PERMISSION_DENIED,
NETWORK_ERROR
};
// 4.2 枚举类
enum class Status {
Success,
Failed,
Timeout
};
void processFile() {
throw ErrorCode::FILE_NOT_FOUND; // 可以,但不推荐
throw Status::Failed; // 可以,但不推荐
}
// 捕获枚举异常
void catchEnums() {
try {
processFile();
} catch (ErrorCode e) {
switch(e) {
case FILE_NOT_FOUND:
std::cout << "File not found" << std::endl;
break;
case PERMISSION_DENIED:
std::cout << "Permission denied" << std::endl;
break;
}
} catch (Status s) {
if (s == Status::Failed) {
std::cout << "Operation failed" << std::endl;
}
}
}
注意事项
异常的处理是通过编译器在来实现的,通过编译器在对应的位置插入代码,其功能与return有些类似,遇到异常后,后续的代码均不会执行,但是会释放函数内部的局部变量。请看下面的例子:
class Resource {
public:
Resource() { cout << "Resource constructed\n"; }
~Resource() { cout << "Resource destroyed\n"; }
};
void f3() {
cout << "f3 start\n";
throw std::runtime_error("error in f3"); // 抛出异常
cout << "f3 end\n"; // 永远不会执行
}
void f2() {
cout << "f2 start\n";
Resource r; // 局部对象会被正确析构
f3(); // 调用抛出异常的函数
cout << "f2 end\n"; // 永远不会执行
}
void f1() {
cout << "f1 start\n";
f2(); // 调用最终抛出异常的函数
cout << "f1 end\n"; // 永远不会执行
}
int main() {
try {
cout << "main start\n";
f1();
cout << "main end\n"; // 永远不会执行
} catch (const std::exception& e) {
cout << "Exception caught: " << e.what() << "\n";
}
}
执行结果如下:
main start
f1 start
f2 start
Resource constructed
f3 start
Resource destroyed
Exception caught: error in f3
使用建议
- 优先使用标准异常类
- 如果需要自定义异常类,需要继承std::exception
- 避免使用基本类型作为异常