C++中抛出标准库异常需使用throw关键字并构造std::exception派生类对象,如std::invalid_argument或std::runtime_error,以提供清晰、统一的错误处理机制;优先选用标准异常类型可提升代码可读性、兼容性和维护性,避免自定义异常带来的复杂性;异常信息应具体、含上下文且具可操作性;通过RAII机制(如智能指针、文件流、锁对象)确保异常发生时资源正确释放,保障异常安全。

在C++中,要抛出标准库异常类型,核心机制是使用
throw
std::exception
抛出标准库异常,实际上就是创建一个
std::exception
以下是一些常用的标准库异常类型及其使用场景:
std::logic_error
立即学习“C++免费学习笔记(深入)”;
std::invalid_argument
std::out_of_range
std::length_error
std::domain_error
std::runtime_error
std::overflow_error
std::underflow_error
std::range_error
std::bad_alloc
new
std::bad_cast
dynamic_cast
抛出异常的语法很简单:
#include <stdexcept> // 包含大部分标准异常类型
#include <iostream>
#include <string>
// 模拟一个处理数据的函数
void process_data(int value) {
if (value < 0) {
// 抛出 invalid_argument 异常,因为参数不合法
throw std::invalid_argument("process_data: Input value cannot be negative.");
}
if (value > 100) {
// 抛出 out_of_range 异常,因为值超出了有效范围
throw std::out_of_range("process_data: Value " + std::to_string(value) + " exceeds maximum limit of 100.");
}
// 假设在特定条件下会发生一个运行时错误
if (value == 50) {
throw std::runtime_error("process_data: A critical runtime error occurred during internal computation.");
}
std::cout << "Successfully processed value: " << value << std::endl;
}
int main() {
// 示例1: 捕获 invalid_argument
try {
process_data(-10);
} catch (const std::invalid_argument& e) {
std::cerr << "Caught std::invalid_argument: " << e.what() << std::endl;
}
// 示例2: 捕获 out_of_range
try {
process_data(120);
} catch (const std::out_of_range& e) {
std::cerr << "Caught std::out_of_range: " << e.what() << std::endl;
}
// 示例3: 捕获 runtime_error
try {
process_data(50);
} catch (const std::runtime_error& e) {
std::cerr << "Caught std::runtime_error: " << e.what() << std::endl;
}
// 示例4: 正常执行
try {
process_data(25);
} catch (const std::exception& e) { // 使用基类捕获,可以捕获所有 std::exception 派生类
std::cerr << "Caught generic std::exception for value 25: " << e.what() << std::endl;
}
// 示例5: 演示 bad_alloc (需要模拟内存耗尽)
// 通常我们不会直接抛出 bad_alloc,它由 new 运算符在内存不足时自动抛出。
// 但为了演示,我们可以手动抛出:
try {
// 假设这里尝试分配一个巨大的数组,并失败了
// int* huge_array = new int[1000000000000ULL]; // 这会直接导致编译或运行时错误,不适合演示
throw std::bad_alloc(); // 模拟内存分配失败
} catch (const std::bad_alloc& e) {
std::cerr << "Caught std::bad_alloc: " << e.what() << std::endl;
}
return 0;
}在实际项目中,我们很少直接抛出
std::exception
在我看来,优先选择标准库异常类型,是一个非常务实且有益的实践。这并不是说自定义异常一无是处,而是说在绝大多数情况下,标准库已经提供了足够丰富且语义清晰的错误类型。
首先,统一性和可预测性是最大的优势。当你的代码抛出
std::invalid_argument
std::runtime_error
e.what()
其次,与标准库的兼容性。许多C++标准库函数本身就会抛出
std::exception
std::vector
std::out_of_range
new
std::bad_alloc
再者,避免过度设计。我发现很多时候,开发者创建自定义异常仅仅是为了给异常起一个更“贴切”的名字,或者为了在异常对象中携带一些额外的、其实通过错误信息字符串就能表达的数据。除非你的异常需要携带复杂的、结构化的、且无法通过
what()
std::runtime_error
std::logic_error
最后,减少重复造轮子。标准库异常体系已经相当完善,覆盖了大多数常见的错误场景。与其花时间设计、实现和维护一套自己的异常体系,不如直接利用现有成熟且经过充分测试的机制。这不仅节省了开发时间,也降低了引入新bug的风险。
抛出异常时,错误信息(也就是传递给异常构造函数的字符串)的质量至关重要。它不仅是给开发者看的,很多时候也会直接呈现在最终用户面前(比如通过日志系统)。一个好的错误信息,能让你在调试时事半功倍,也能帮助用户理解问题。
我的经验是,有效的错误信息应该具备以下几个特点:
"Failed to open configuration file 'settings.json' because file not found."
"File error."
"User ID '12345' not found in database during user profile lookup."
"Database connection pool exhausted for server 'db.example.com'."
"SQLSTATE 08006 connection_refused"
"Insufficient permissions to write to directory '/var/log'. Please check file system permissions."
// 错误信息示例
std::string filename = "non_existent.txt";
// 不好的错误信息
// throw std::runtime_error("File operation failed.");
// 好的错误信息
throw std::runtime_error("Failed to open file '" + filename + "' for writing. Check path and permissions.");
int index = 10;
std::vector<int> data = {1, 2, 3};
// 不好的错误信息
// throw std::out_of_range("Index error.");
// 好的错误信息
throw std::out_of_range("Attempted to access vector at index " + std::to_string(index) + ", but vector size is " + std::to_string(data.size()) + ".");组织好错误信息,能够显著提升代码的可维护性和调试效率。
在C++中,当异常被抛出时,程序的控制流会发生非局部跳转,这意味着当前作用域内的局部变量可能无法正常执行到它们的析构函数。如果这些局部变量管理着资源(如内存、文件句柄、网络连接、锁等),那么这些资源就可能发生泄漏。这就是异常安全的核心挑战。
C++解决这个问题的黄金法则就是RAII(Resource Acquisition Is Initialization)。RAII的理念很简单:将资源的生命周期与对象的生命周期绑定。当对象被创建时(初始化时),它获取资源;当对象被销毁时(析构时),它释放资源。由于C++保证局部对象的析构函数在异常抛出时仍然会被调用(栈展开),因此只要资源被RAII对象管理,就能确保在异常发生时资源被正确释放,避免泄漏。
具体实践上:
智能指针管理动态内存:
std::unique_ptr
unique_ptr
std::shared_ptr
shared_ptr
new
new
delete
delete
#include <memory>
#include <stdexcept>
void risky_operation() {
// 使用 unique_ptr 管理内存,即使抛出异常也能自动释放
std::unique_ptr<int[]> data = std::make_unique<int[]>(100);
// ... 其他操作,可能抛出异常 ...
if (true /* 某个条件导致异常 */) {
throw std::runtime_error("Something went wrong!");
}
// data 会在函数退出或异常抛出时自动析构,释放内存
}文件流自动关闭:
std::ifstream
std::ofstream
#include <fstream>
#include <stdexcept>
void process_file(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + path);
}
// ... 读取或写入文件,可能抛出异常 ...
// file 对象会在函数退出或异常抛出时自动析构,关闭文件
}锁管理:
std::lock_guard
std::unique_lock
#include <mutex>
#include <stdexcept>
std::mutex mtx;
void access_shared_resource() {
std::lock_guard<std::mutex> lock(mtx); // 自动获取锁
// ... 访问共享资源,可能抛出异常 ...
if (true /* 某个条件导致异常 */) {
throw std::runtime_error("Error during resource access!");
}
// lock 对象会在函数退出或异常抛出时自动析构,释放锁
}避免在析构函数中抛出异常:
std::terminate
close()
release()
通过始终遵循RAII原则,将所有资源包装在具有适当析构行为的对象中,我们可以构建出异常安全的代码,大大减少资源泄漏的风险。这是C++异常处理机制与语言特性相结合的强大体现。
以上就是C++如何抛出标准库异常类型的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号