应结合使用异常和错误码,底层错误码在高层不可预期时转换为异常,高层用异常简化流程,可预期失败用错误码或std::expected,通过自定义异常、统一策略和RAII保持代码清晰。

在C++的世界里,错误处理这事儿,总让人有点纠结。我们手头有两把利器:异常处理(Exceptions)和错误码返回(Error Codes)。它们各有千秋,也各有适用的场景。与其争论谁更优越,不如思考如何将它们巧妙地结合起来,让我们的代码既健壮又易于维护。在我看来,这并不是非此即彼的选择题,而是一门关于权衡和策略的艺术。核心观点是,将异常用于真正“异常”的、不可预期的、需要立即中断执行的情况,而将错误码用于那些可预期的、需要局部处理或重试的“正常”失败流程。
结合使用C++异常处理和错误码返回,通常遵循一种分层或混合策略。这意味着在系统的不同抽象层级或针对不同类型的错误,采用最适合的机制。一个常见的做法是,在底层与系统API或库交互时,我们可能不得不处理它们返回的错误码(比如
errno
HRESULT
具体来说,可以这样操作:
std::optional
Result
try-catch
std::expected
Result
std::expected<T, E>
Result<T, E>
T
E
这确实是一个需要深思熟虑的问题,没有一刀切的答案,更多的是一种哲学选择。我的个人经验告诉我,这取决于错误的“性质”和“预期性”。
立即学习“C++免费学习笔记(深入)”;
优先使用异常处理的场景:
std::invalid_argument
优先选择错误码的场景:
errno
std::expected
将底层错误码转换为C++异常,关键在于“语义化”和“封装”。我们不希望仅仅把
errno
一个行之有效的方法是创建自定义异常类。这些类应该继承自
std::exception
std::runtime_error
std::logic_error
#include <stdexcept>
#include <string>
#include <system_error> // For std::system_error and std::error_code
#include <iostream>
#include <fstream> // For file operations
#include <vector>
// 1. 定义自定义异常类
class FileOperationException : public std::runtime_error {
public:
// 可以存储原始错误码,方便调试
explicit FileOperationException(const std::string& message, int errorCode = 0)
: std::runtime_error(message), originalErrorCode_(errorCode) {}
int getOriginalErrorCode() const { return originalErrorCode_; }
private:
int originalErrorCode_;
};
class FileNotFoundError : public FileOperationException {
public:
explicit FileNotFoundError(const std::string& message, int errorCode = 0)
: FileOperationException(message, errorCode) {}
};
class PermissionDeniedError : public FileOperationException {
public:
explicit PermissionDeniedError(const std::string& message, int errorCode = 0)
: FileOperationException(message, errorCode) {}
};
// 2. 封装底层C风格文件操作,将errno转换为C++异常
void openAndReadFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
// 在这里,我们通常无法直接获取errno,因为std::ifstream封装了它
// 但如果是一个C风格的open(),我们可以这样做:
// int fd = open(filename.c_str(), O_RDONLY);
// if (fd == -1) {
// int err = errno; // 捕获原始errno
// if (err == ENOENT) {
// throw FileNotFoundError("Failed to open file: " + filename + ". File does not exist.", err);
// } else if (err == EACCES) {
// throw PermissionDeniedError("Failed to open file: " + filename + ". Permission denied.", err);
// } else {
// throw FileOperationException("Failed to open file: " + filename + ". System error code: " + std::to_string(err), err);
// }
// }
// For std::ifstream, we might infer or provide a more generic message
throw FileOperationException("Failed to open file: " + filename + ". Check path and permissions.");
}
std::string line;
while (std::getline(file, line)) {
std::cout << line << std::endl;
}
// std::ifstream 在析构时会自动关闭文件,符合RAII
}
// 另一个例子:处理一个假想的返回错误码的API
enum class NetworkErrorCode {
Success = 0,
ConnectionRefused,
Timeout,
InvalidHost,
UnknownError
};
NetworkErrorCode connectToServer(const std::string& host, int port) {
if (host == "bad.host") return NetworkErrorCode::InvalidHost;
if (port == 8080) return NetworkErrorCode::ConnectionRefused; // Simulate connection refused
if (port == 9000) return NetworkErrorCode::Timeout; // Simulate timeout
return NetworkErrorCode::Success;
}
// 封装并转换网络错误码
void establishNetworkConnection(const std::string& host, int port) {
NetworkErrorCode ec = connectToServer(host, port);
if (ec != NetworkErrorCode::Success) {
std::string message = "Network connection failed to " + host + ":" + std::to_string(port) + ". ";
switch (ec) {
case NetworkErrorCode::ConnectionRefused:
throw std::runtime_error(message + "Connection refused.");
case NetworkErrorCode::Timeout:
throw std::runtime_error(message + "Timeout occurred.");
case NetworkErrorCode::InvalidHost:
throw std::invalid_argument(message + "Invalid host specified.");
case NetworkErrorCode::UnknownError:
default:
throw std::runtime_error(message + "Unknown network error.");
}
}
std::cout << "Successfully connected to " << host << ":" << port << std::endl;
}
// 示例使用
int main() {
std::cout << "--- File Operations ---" << std::endl;
try {
openAndReadFile("non_existent_file.txt");
} catch (const FileNotFoundError& e) {
std::cerr << "Caught FileNotFoundError: " << e.what() << " (Error code: " << e.getOriginalErrorCode() << ")" << std::endl;
} catch (const FileOperationException& e) {
std::cerr << "Caught FileOperationException: " << e.what() << " (Error code: " << e.getOriginalErrorCode() << ")" << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught general exception: " << e.what() << std::endl;
}
std::cout << "\n--- Network Operations ---" << std::endl;
try {
establishNetworkConnection("good.host", 8080); // Will simulate connection refused
} catch (const std::invalid_argument& e) {
std::cerr << "Caught invalid argument: " << e.what() << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "Caught runtime error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught general exception: " << e.what() << std::endl;
}
try {
establishNetworkConnection("bad.host", 1234); // Will simulate invalid host
} catch (const std::invalid_argument& e) {
std::cerr << "Caught invalid argument: " << e.what() << std::endl;
}
try {
establishNetworkConnection("good.host", 1234); // Success
} catch (const std::exception& e) {
std::cerr << "Caught unexpected exception: " << e.what() << std::endl;
}
return 0;
}这段代码展示了如何通过自定义异常类来封装底层错误码,并在更高的抽象层级抛出具有语义的异常。这样,上层调用者可以根据具体的异常类型进行更精细的错误处理,而不是仅仅看到一个数字。
在一个同时使用异常和错误码的复杂系统中,保持代码的清晰性和可读性确实是个挑战。如果处理不当,代码会变得混乱不堪,错误处理逻辑也难以追踪。我的经验告诉我,关键在于一致性、明确的策略和良好的文档。
Result
[[nodiscard]]
noexcept
Result
enum class
[[nodiscard]]
std::bad_alloc
ErrorCode::FileNotFound
通过这些实践,我们可以在一个混合的错误处理体系中,构建出既高效又易于理解和维护的代码。关键在于有意识地设计,而不是随意地将两者混用。
以上就是C++异常处理与错误码返回结合使用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号