C++程序设计语言笔记——基本功能:非常处置处罚
0 在设计初期尽早确定非常处置处罚战略。在C++中,设计初期的非常处置处罚战略必要细密结合语言特性(如RAII、非常安全品级、智能指针)和性能要求。以下是一套针对C++的非常处置处罚设计框架,包罗代码示例和最佳实践:
1. 非常分类与标准化设计
1.1 非常类型层级
#include <stdexcept>
#include <string>
// 自定义业务异常基类(继承标准异常)
class BusinessException : public std::runtime_error {
public:
explicit BusinessException(const std::string& msg, int code = 0)
: std::runtime_error(msg), error_code(code) {}
int get_code() const noexcept { return error_code; }
private:
int error_code;
};
// 具体业务异常(示例:订单异常)
class OrderException : public BusinessException {
public:
enum ErrorCode { NOT_FOUND = 1001, INVALID_STATE = 1002 };
explicit OrderException(ErrorCode code, const std::string& details = "")
: BusinessException("OrderError: " + details, code) {}
};
1.2 错误码规范
// 使用强类型enum定义错误域
namespace ErrorDomain {
enum class Database { CONNECTION_FAILED = 2001, TIMEOUT = 2002 };
enum class Network { API_FAILURE = 3001, RATE_LIMITED = 3002 };
}
// 异常中携带错误域信息
class TechnicalException : public std::runtime_error {
public:
template <typename T>
TechnicalException(T code, const std::string& msg)
: std::runtime_error(msg), error_code(static_cast<int>(code)) {}
int get_code() const noexcept { return error_code; }
private:
int error_code;
};
2. 非常处置处罚机制
2.1 全局非常处置处罚
#include <iostream>
#include <cstdlib>
// 设置全局异常处理器(适用于未被捕获的异常)
void global_handler() {
try {
if (auto ex = std::current_exception()) {
std::rethrow_exception(ex);
}
} catch (const BusinessException& e) {
std::cerr << " Code: " << e.get_code()
<< ", Msg: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cerr << " " << e.what() << "\n";
}
// 安全终止或重启逻辑
std::abort();
}
int main() {
std::set_terminate(global_handler);
// 主逻辑...
}
2.2 防御性编程与左券
// 使用GSL(Guidelines Support Library)进行契约检查
#include <gsl/gsl_assert>
void process_order(Order& order) {
Expects(order.is_valid()); // 前置条件检查,失败则终止
// ...
}
// 参数校验(抛出受检异常)
void validate_input(const std::string& input) {
if (input.empty()) {
throw BusinessException("Input cannot be empty", 400);
}
}
3. 资源管理与非常安全
3.1 RAII模式保障资源开释
class DatabaseConnection {
public:
DatabaseConnection() {
if (!connect()) throw TechnicalException(ErrorDomain::Database::CONNECTION_FAILED, "DB unreachable");
}
~DatabaseConnection() noexcept { disconnect(); }
// 禁用拷贝,允许移动
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
DatabaseConnection(DatabaseConnection&&) = default;
DatabaseConnection& operator=(DatabaseConnection&&) = default;
private:
bool connect() { /* ... */ }
void disconnect() noexcept { /* ... */ }
};
// 使用示例
void query_data() {
DatabaseConnection conn; // 资源获取即初始化
// 若此处抛出异常,conn析构会自动调用disconnect()
// ...
}
3.2 智能指针管理动态资源
void process_file(const std::string& path) {
auto file = std::make_unique<std::ifstream>(path);
if (!file->is_open()) {
throw TechnicalException(ErrorDomain::IO::FILE_OPEN_FAILED, path);
}
// 即使后续操作抛出异常,unique_ptr会确保文件关闭
// ...
}
4. 性能优化与高级本领
4.1 noexcept与移动语义
class Buffer {
public:
Buffer(size_t size) : data_(new char) {}
// 移动构造函数标记为noexcept,确保容器操作安全
Buffer(Buffer&& other) noexcept : data_(std::exchange(other.data_, nullptr)) {}
~Buffer() noexcept { delete[] data_; }
private:
char* data_;
};
// 标记不会失败的函数
void log_message(const std::string& msg) noexcept {
// 即使此处抛出异常,程序会终止(符合noexcept约定)
std::cout << msg << "\n";
}
4.2 非常替代方案(可选)
// 使用std::optional返回可能失败的操作
std::optional<int> safe_divide(int a, int b) {
if (b == 0) return std::nullopt;
return a / b;
}
// 使用std::expected(C++23或第三方库)
template <typename T, typename E>
using expected = /* 类似Rust Result类型的实现 */;
expected<Data, Error> fetch_data() {
if (/* 失败条件 */) return unexpected(Error::NETWORK_FAILURE);
return Data{/* ... */};
}
5. 测试与调试战略
5.1 单元测试非常路径
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("Order validation throws on invalid state") {
Order order(/* 无效状态 */);
REQUIRE_THROWS_AS(order.confirm(), OrderException);
REQUIRE_THROWS_WITH(order.cancel(), "Order is already completed");
}
5.2 静态分析工具
# 使用Clang-Tidy检查异常安全
clang-tidy -checks='modernize-use-noexcept, bugprone-exception-escape' src/*.cpp
6. 设计原则总结
原则C++实现方法示例工具/语法资源安全RAII + 智能指针unique_ptr, 自定义RAII类非常中立确保析构函数不抛非常~MyClass() noexcept {…}强非常包管先修改副本,再swapstd::swap, copy-and-swap性能敏感区禁用非常,改用错误码(-fno-exceptions)Google的禁用非常规范跨模块边界C接口封装非常为错误码extern “C” + try-catch块 完整示例:非常安全的事务操纵
class Transaction {
public:
void execute() {
auto backup = current_state_; // 强异常保证:先准备副本
modify_state(); // 可能抛出异常的操作
current_state_.swap(backup);// 无异常则提交更改
}
private:
State current_state_;
void modify_state() { /* ...可能抛出... */ }
};
void business_operation() {
try {
Transaction tx;
tx.execute();
} catch (const BusinessException& e) {
// 业务级恢复逻辑
logger.log_rollback(e);
throw; // 重新抛出给上层
}
}
决议树:何时利用非常?
是否违反函数的前置条件? ——是——> 使用断言(Expects/Ensures)
|
否
|
是否可恢复的预期错误? ————是——> 抛出业务异常
|
否
|
是否程序无法继续执行? ————是——> 抛技术异常/终止
|
否
|
使用错误码或optional
通过早期建立这些战略,可使C++项目在维护性、性能和安全之间取得平衡。发起将核心规则写入团队的《C++非常处置处罚指南》,并通过代码审查确保一致性。
1 当无法完成既定使命时抛出非常。
在C++中,当函数无法完成其承诺的使命时,抛出非常是一种清晰的错误流传方式。以下是针对这一场景的详细设计战略和代码实现指南:
1. 非常抛出原则
1.1 明确抛出条件
// 示例:文件加载失败时抛出异常
class FileLoader {
public:
std::string load(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw FileIOException("Cannot open file: " + path, ErrorCode::FILE_NOT_FOUND);
}
// 读取文件内容...
if (file.bad()) {
throw FileIOException("Read error", ErrorCode::IO_ERROR);
}
return content;
}
};
1.2 利用标准非常类型或继承体系
#include <stdexcept>
#include <string>
// 自定义异常类型(继承自std::runtime_error)
class NetworkException : public std::runtime_error {
public:
enum class ErrorCode { TIMEOUT, CONNECTION_REFUSED };
NetworkException(ErrorCode code, const std::string& details)
: std::runtime_error(details), code_(code) {}
ErrorCode code() const noexcept { return code_; }
private:
ErrorCode code_;
};
// 使用示例
void connect_to_server() {
if (/* 连接超时 */) {
throw NetworkException(NetworkException::ErrorCode::TIMEOUT, "Timeout after 30s");
}
}
2. 非常安全品级设计
2.1 基本非常安全(Basic Guarantee)
class DatabaseTransaction {
public:
void execute() {
auto old_state = current_state_; // 备份状态
try {
modify_database(); // 可能抛出异常的操作
current_state_ = new_state_;
} catch (...) {
current_state_ = old_state; // 回滚到之前状态
throw;
}
}
};
2.2 强非常安全(Strong Guarantee)
class ConfigManager {
public:
void update_config(const Config& new_config) {
auto temp = std::make_unique<Config>(new_config); // 先在临时对象操作
validate_config(*temp);// 可能抛出
config_.swap(temp); // 无异常则提交(原子操作)
}
};
3. 资源管理战略
3.1 RAII主动开释资源
class SafeFileHandle {
public:
explicit SafeFileHandle(const std::string& path)
: handle_(fopen(path.c_str(), "r")) {
if (!handle_) {
throw FileIOException("Open failed: " + path);
}
}
~SafeFileHandle() noexcept {
if (handle_) fclose(handle_);
}
// 禁用拷贝,允许移动
SafeFileHandle(const SafeFileHandle&) = delete;
SafeFileHandle& operator=(const SafeFileHandle&) = delete;
SafeFileHandle(SafeFileHandle&&) = default;
SafeFileHandle& operator=(SafeFileHandle&&) = default;
private:
FILE* handle_;
};
// 使用示例
void process_file() {
SafeFileHandle file("data.bin"); // 异常安全
// 使用文件句柄...
}
4. 错误类型与上下文传递
4.1 携带详细错误信息
class PaymentException : public std::runtime_error {
public:
PaymentException(const std::string& msg,
int user_id,
const std::string& order_no)
: std::runtime_error(msg),
user_id_(user_id),
order_no_(order_no) {}
int user_id() const noexcept { return user_id_; }
const std::string& order_no() const noexcept { return order_no_; }
private:
int user_id_;
std::string order_no_;
};
// 抛出示例
void process_payment(int user_id, const Order& order) {
if (order.amount <= 0) {
throw PaymentException("Invalid amount", user_id, order.id);
}
}
5. 非常处置处罚最佳实践
5.1 顶层非常捕获
int main() {
try {
run_application(); // 主业务逻辑
} catch (const NetworkException& ex) {
std::cerr << "Network Error: " << ex.what()
<< " Code: " << static_cast<int>(ex.code()) << "\n";
return 1;
} catch (const std::exception& ex) {
std::cerr << "Fatal Error: " << ex.what() << "\n";
return 2;
} catch (...) {
std::cerr << "Unknown exception occurred\n";
return 3;
}
return 0;
}
5.2 非常与日记集成
void handle_request() {
try {
// 业务逻辑...
} catch (const BusinessException& ex) {
logger.error("Business failure: {}", ex.what());
throw;
} catch (const std::exception& ex) {
logger.error("Technical failure: {}", ex.what());
throw AppException("Internal error"); // 包装为通用异常
}
}
6. 性能敏感场景替代方案
6.1 禁用非常(-fno-exceptions)
// 使用错误码返回结果
enum class ErrorCode { OK, FILE_NOT_FOUND, INVALID_DATA };
ErrorCode parse_data(const std::string& input, Data& output) {
if (input.empty()) return ErrorCode::INVALID_DATA;
// 解析逻辑...
return ErrorCode::OK;
}
// 调用方处理
ErrorCode err = parse_data(raw_input, data);
if (err != ErrorCode::OK) {
handle_error(err);
}
6.2 利用std::optional(C++17)
std::optional<Image> load_image(const std::string& path) {
if (!file_exists(path)) return std::nullopt;
// 加载图像...
return Image(pixels);
}
// 调用方
auto img = load_image("photo.jpg");
if (!img) {
show_error("Image not loaded");
}
7. 非常测试战略
7.1 单元测试非常路径
#include <catch2/catch.hpp>
TEST_CASE("Invalid payment amount throws") {
Order test_order{ .id = "A1001", .amount = -50 };
REQUIRE_THROWS_AS(process_payment(101, test_order), PaymentException);
}
TEST_CASE("Network timeout handled") {
mock_server.set_response_delay(60s); // 模拟超时
REQUIRE_THROWS_MATCHES(
connect_to_server(),
NetworkException,
Catch::Matchers::Message("Timeout after 30s")
);
}
决议表:何时抛出非常?
场景处置处罚方式示例违反函数前置条件抛出非常或终止程序if (ptr == nullptr) throw ...关键资源获取失败(文件、网络)立即抛出非常文件打开失败、数据库毗连断开外部服务返回不可规复错误抛出携带上下文的业务非常付出网关返回余额不敷数据校验失败且无法继承流程抛出带错误码的验证非常用户提交的表单数据非法必要跨多层调用处置处罚错误抛出非常而非逐层返回错误码深层嵌套的业务逻辑错误 关键设计总结
[*]明确语义:每个非常类型对应详细的错误场景,避免通用std::exception直接抛出
[*]资源安全:结合RAII和智能指针确保非常发生时资源主动开释
[*]性能衡量:在及时体系中评估非常开销,须要时利用错误码
[*]文档左券:在函数注释中明确说明可能抛出的非常类型
[*]统一处置处罚:在应用入口集中捕获未处置处罚非常,记录日记后安全退出
通过遵循这些准则,可在C++项目中构建清晰、健壮的非常处置处罚体系,使错误处置处罚逻辑成为代码质量的上风而非负担。
2 用非常机制处置处罚错误。
在C++中,利用非常机制处置处罚错误是一种有效的方法,能够在错误发生时将控制流转移到合适的处置处罚代码,保持程序结构的清晰和健壮性。以下是分步说明和示例:
1. 定义非常类
继承自标准非常类(如std::runtime_error),以携带特定错误信息。
#include <stdexcept>
#include <string>
// 自定义异常类示例
class FileIOException : public std::runtime_error {
public:
enum class ErrorCode { FileNotFound, PermissionDenied, ReadError };
FileIOException(const std::string& message, ErrorCode code)
: std::runtime_error(message), error_code(code) {}
ErrorCode getCode() const noexcept { return error_code; }
private:
ErrorCode error_code;
};
2. 抛出非常
在检测到错误时,利用throw关键字抛出非常对象。
#include <fstream>
void loadFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw FileIOException("无法打开文件: " + filename,
FileIOException::ErrorCode::FileNotFound);
}
// 读取文件内容...
if (file.fail()) {
throw FileIOException("读取文件失败",
FileIOException::ErrorCode::ReadError);
}
}
3. 捕获并处置处罚非常
利用try-catch块捕获非常,并根据类型处置处罚。
int main() {
try {
loadFile("data.txt");
} catch (const FileIOException& e) {
std::cerr << "文件错误: " << e.what()
<< ",错误码: " << static_cast<int>(e.getCode()) << std::endl;
// 处理文件错误,如重试或退出
return 1;
} catch (const std::exception& e) {
std::cerr << "标准异常: " << e.what() << std::endl;
return 2;
} catch (...) {
std::cerr << "未知异常发生!" << std::endl;
return 3;
}
return 0;
}
4. 资源管理(RAII)
利用对象的析构函数主动开释资源,避免资源走漏。
class SafeFileHandler {
public:
explicit SafeFileHandler(const std::string& filename)
: file_(filename) {
if (!file_.is_open()) {
throw FileIOException("文件打开失败",
FileIOException::ErrorCode::FileNotFound);
}
}
~SafeFileHandler() {
if (file_.is_open()) {
file_.close(); // 确保文件关闭
}
}
// 禁用拷贝,允许移动
SafeFileHandler(const SafeFileHandler&) = delete;
SafeFileHandler& operator=(const SafeFileHandler&) = delete;
SafeFileHandler(SafeFileHandler&&) = default;
SafeFileHandler& operator=(SafeFileHandler&&) = default;
void readData() {
// 读取操作,可能抛出异常
}
private:
std::ifstream file_;
};
void processFile() {
SafeFileHandler file("data.txt"); // RAII管理资源
file.readData(); // 即使此处抛出异常,file的析构函数仍会关闭文件
}
5. 非常安全包管
确保操纵在非常发生后仍保持数据一致性。
强非常安全示例(Copy-and-Swap)
class DatabaseTransaction {
public:
void updateRecord(int id, const std::string& newData) {
auto oldData = data_; // 备份当前数据
data_ = newData; // 修改数据(可能失败的操作)
if (!commitToDatabase()) {
data_ = oldData; // 失败则恢复
throw std::runtime_error("提交数据库失败");
}
}
private:
std::string data_;
bool commitToDatabase() { /* 模拟数据库提交 */ return false; }
};
6. 性能优化
在性能敏感代码中,避免频仍抛出非常,改用错误码。
// 返回错误码而非抛出异常
enum class ErrorCode { Success, InvalidInput, NetworkError };
ErrorCode processRequest(Request& req) {
if (!req.isValid()) {
return ErrorCode::InvalidInput;
}
// 处理请求...
return ErrorCode::Success;
}
// 调用方处理
ErrorCode result = processRequest(request);
if (result != ErrorCode::Success) {
handleError(result);
}
7. 测试非常路径
利用测试框架验证非常是否正确抛出。
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("加载不存在的文件应抛出异常") {
REQUIRE_THROWS_AS(loadFile("nonexistent.txt"), FileIOException);
}
TEST_CASE("无效输入返回错误码") {
Request invalidReq;
CHECK(processRequest(invalidReq) == ErrorCode::InvalidInput);
}
总结
[*]明确非常用途:仅对不可规复或关键错误利用非常。
[*]资源管理:结合RAII确保资源安全开释。
[*]非常安全:设计操纵包管基本或强非常安全。
[*]分层处置处罚:在得当层级捕获非常(如UI层捕获并显示错误)。
[*]性能考量:避免在频仍实验路径中利用非常。
[*]文档化:在函数注释中注明可能抛出的非常类型。
通过公道利用非常机制,可以明显提升代码的可读性和健壮性,将错误处置处罚逻辑与业务逻辑分离,便于维护和调试。
3 为特定使命设计用户自定义非常类型(而非内置类型)。
在设计用户自定义非常类型时,遵循以下步调可以确保其高效性、安全性和易用性:
1. 定义底子非常类
创建基类 BaseException,包罗核心属性和方法,供所有详细非常继承。
#include <string>
#include <exception> // 可选,若不继承标准异常则无需包含
// 完全独立的自定义异常基类(不继承std::exception)
class BaseException {
public:
BaseException(const std::string& message, int code = 0)
: message_(message), error_code_(code) {}
virtual ~BaseException() = default; // 虚析构函数防止切片
virtual const char* what() const noexcept {
return message_.c_str();
}
int code() const noexcept {
return error_code_;
}
protected:
std::string message_;
int error_code_;
};
2. 创建特定使命非常类
针对差别错误场景,派生详细的非常类,添加使命相关数据。
示例1:文件操纵非常
class FileIOException : public BaseException {
public:
enum class Operation { Read, Write, Open };
FileIOException(Operation op, const std::string& path, int sys_errno = 0)
: BaseException(formatMessage(op, path, sys_errno), sys_errno),
operation_(op), file_path_(path) {}
Operation operation() const { return operation_; }
const std::string& path() const { return file_path_; }
private:
static std::string formatMessage(Operation op, const std::string& path, int err) {
std::string opStr;
switch (op) {
case Operation::Read: opStr = "读取"; break;
case Operation::Write: opStr = "写入"; break;
case Operation::Open: opStr = "打开"; break;
}
return opStr + "文件失败: " + path + " (系统错误码: " + std::to_string(err) + ")";
}
Operation operation_;
std::string file_path_;
};
示例2:网络请求非常
#include <chrono>
class NetworkException : public BaseException {
public:
NetworkException(const std::string& url,
const std::string& response,
int http_status)
: BaseException("HTTP请求失败: " + url + " [状态码: " + std::to_string(http_status) + "]", http_status),
url_(url), response_(response), http_status_(http_status),
timestamp_(std::chrono::system_clock::now()) {}
const std::string& url() const { return url_; }
const std::string& response() const { return response_; }
std::time_t timestamp() const {
return std::chrono::system_clock::to_time_t(timestamp_);
}
private:
std::string url_;
std::string response_;
int http_status_;
std::chrono::system_clock::time_point timestamp_;
};
3. 抛出非常
在检测到错误时,构造并抛出详细非常对象。
#include <fstream>
#include <cstring> // 用于strerror
void readFile(const std::string& path) {
std::ifstream file(path);
if (!file) {
throw FileIOException(FileIOException::Operation::Open, path, errno);
}
std::string content;
if (!std::getline(file, content)) {
throw FileIOException(FileIOException::Operation::Read, path, errno);
}
}
4. 捕获并处置处罚非常
利用try-catch块按类型处置处罚差别非常,访问其特定属性。
int main() {
try {
readFile("data.txt");
// 假设此处有网络请求...
} catch (const FileIOException& e) {
std::cerr << "[文件错误] 操作类型: " << static_cast<int>(e.operation())
<< "\n路径: " << e.path()
<< "\n错误信息: " << e.what() << std::endl;
} catch (const NetworkException& e) {
std::cerr << "[网络错误] URL: " << e.url()
<< "\n响应内容: " << e.response()
<< "\n时间: " << std::ctime(&e.timestamp())
<< "错误码: " << e.code() << std::endl;
} catch (const BaseException& e) {
std::cerr << "[通用错误] " << e.what()
<< " (代码: " << e.code() << ")" << std::endl;
} catch (...) {
std::cerr << "未知异常发生!" << std::endl;
}
return 0;
}
5. 高级特性增强
5.1 支持链式非常(错误缘故原由追溯)
class BaseException {
public:
BaseException(const std::string& message, BaseException* cause = nullptr)
: message_(message), cause_(cause) {}
const BaseException* cause() const { return cause_.get(); }
// 递归打印异常链
void printTrace(std::ostream& os, int level = 0) const {
os << std::string(level * 2, ' ') << "[" << level << "] " << what() << "\n";
if (cause_) {
cause_->printTrace(os, level + 1);
}
}
private:
std::unique_ptr<BaseException> cause_;
};
// 使用示例
try {
try {
connectDatabase(); // 可能抛出NetworkException
} catch (BaseException& e) {
throw BaseException("数据库连接失败", new NetworkException(e)); // 包装原因
}
} catch (const BaseException& e) {
e.printTrace(std::cerr);
}
5.2 序列化非常信息(用于日记或网络传输)
#include <nlohmann/json.hpp> // 使用JSON库
class NetworkException : public BaseException {
public:
nlohmann::json toJson() const {
return {
{"type", "NetworkException"},
{"url", url_},
{"http_status", http_status_},
{"timestamp", timestamp_.time_since_epoch().count()},
{"message", message_},
{"code", error_code_}
};
}
};
// 日志记录示例
catch (const NetworkException& e) {
logger.error(e.toJson().dump());
}
6. 设计原则总结
原则实现方法明确语义每个非常类对应详细错误场景(如FileIOException、NetworkException)丰富上下文在非常类中添加使命相关数据(路径、错误码、时间戳等)类型安全通过继承层次区分错误类型,避免利用通用非常资源安全利用智能指针管理动态分配的缘故原由非常(std::unique_ptr<BaseException>)可扩展性通过基类提供统一接口(what()、code()),派生类扩展数据兼容性可选:若需与标准非常交互,可多重继承std::exception(但根据用户需求省略) 性能优化发起
[*]避免频仍抛出:在性能关键路径(如循环内部)优先利用错误码。
[*]移动语义:确保非常类支持移动构造,减少拷贝开销。class BaseException {
public:
BaseException(BaseException&&) = default;
BaseException& operator=(BaseException&&) = default;
};
[*]轻量级设计:避免在非常类中存储过大数据(如完整响应内容可存指针)。
单元测试示例
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("读取不存在文件应抛出FileIOException") {
REQUIRE_THROWS_AS(readFile("nonexistent.txt"), FileIOException);
}
TEST_CASE("NetworkException序列化包含正确URL") {
NetworkException e("https://api.example.com", "Not Found", 404);
auto json = e.toJson();
REQUIRE(json["url"] == "https://api.example.com");
REQUIRE(json["http_status"] == 404);
}
通过以上步调,您可以为特定使命设计出功能强大、信息丰富的自定义非常类型,提升代码的可维护性和错误处置处罚能力。
4 假如由于某种缘故原由你无法利用非常,尽量模仿其机制。
在无法利用C++非常机制的情况下(如嵌入式环境、禁用非常的代码规范或性能敏感场景),可以通过以下方法模仿类似非常的错误处置处罚流程,同时保持代码的结构化和安全性:
1. 定义错误类型与返回码
1.1 通用错误码罗列
enum class ErrorCode {
Success = 0,
FileNotFound,
NetworkTimeout,
InvalidArgument,
OutOfMemory
};
1.2 携带上下文信息的错误对象
struct Error {
ErrorCode code;
std::string message;// 错误描述
std::string detail; // 调试信息(如文件路径)
int sys_errno = 0; // 系统错误码(如errno)
// 快速创建错误的辅助方法
static Error fromFileError(ErrorCode code, const std::string& path, int err) {
return Error{code, "文件操作失败", "路径: " + path, err};
}
};
2. 错误传递机制
2.1 函数返回错误码
// 返回值 + 错误码输出参数
bool readFile(std::string& content, const std::string& path, Error& err) {
if (path.empty()) {
err = Error{ErrorCode::InvalidArgument, "路径为空"};
return false;
}
// 文件操作...
return true;
}
2.2 利用结构体包装结果
template <typename T>
struct Result {
T value;
Error error;
bool ok() const { return error.code == ErrorCode::Success; }
};
Result<std::string> loadConfig(const std::string& path) {
if (!fileExists(path)) {
return { {}, Error::fromFileError(ErrorCode::FileNotFound, path, errno) };
}
return { readFileContent(path), Error{ErrorCode::Success} };
}
3. 错误处置处罚流程模仿
3.1 手动实现"try-catch"逻辑
// 通过宏简化错误检查
#define TRY(expr) \
{ auto result = expr; if (!result.ok()) return result.error; }
// 函数调用链中的错误冒泡
Error initializeSystem() {
TRY(loadConfig("config.json"));// 若失败直接返回错误
TRY(connectToDatabase());
return Error{ErrorCode::Success};
}
3.2 错误处置处罚中央
void handleError(const Error& err) {
logError(err);// 记录日志
if (err.code == ErrorCode::NetworkTimeout) {
retryOperation();
} else {
shutdownGracefully();
}
}
int main() {
Error err = initializeSystem();
if (!err.ok()) {
handleError(err);
return 1;
}
return 0;
}
4. 资源管理(模仿RAII)
4.1 自定义作用域保卫
template <typename Cleanup>
class ScopeGuard {
public:
ScopeGuard(Cleanup cleanup) : cleanup_(cleanup), active_(true) {}
~ScopeGuard() { if (active_) cleanup_(); }
void dismiss() { active_ = false; }
private:
Cleanup cleanup_;
bool active_;
};
// 使用示例:确保文件句柄关闭
Result<void> processFile(const std::string& path) {
FILE* file = fopen(path.c_str(), "r");
if (!file) return makeFileError(path);
auto guard = ScopeGuard([&] { fclose(file); });
// 文件操作...
guard.dismiss();// 操作成功时取消关闭
return success();
}
5. 错误流传优化
5.1 错误链追踪
struct Error {
// ... 其他字段
std::shared_ptr<Error> cause;// 错误原因链
static Error wrap(Error&& current, Error&& cause) {
current.cause = std::make_shared<Error>(std::move(cause));
return current;
}
};
Error fetchData() {
Error err = downloadFromNetwork();
if (!err.ok()) {
return Error::wrap(
Error{ErrorCode::DataUnavailable, "数据获取失败"},
std::move(err)
);
}
return success();
}
6. 替代方案对比
方法优点缺点返回错误码简朴直观,零开销需手动查抄每个返回值Result结构体类型安全,可携带附加数据代码冗余,需频仍解包setjmp/longjmp真正的非本地跳转不安全(可能跳过析构函数)错误回调函数异步友好粉碎控制流可读性 完整示例:错误处置处罚工作流
Result<int> parseNumber(const std::string& input) {
if (input.empty()) {
return {0, Error{ErrorCode::InvalidArgument, "输入为空"}};
}
try {
return {std::stoi(input), {}};
} catch (const std::exception& e) {
return {0, Error{ErrorCode::InvalidArgument, e.what()}};
}
}
Error calculateSum() {
Result<int> a = parseNumber("42");
if (!a.ok()) return a.error;
Result<int> b = parseNumber("abc");
if (!b.ok()) return b.error;// 此处会返回解析错误
std::cout << "Sum: " << (a.value + b.value) << std::endl;
return {};
}
int main() {
if (Error err = calculateSum(); !err.ok()) {
std::cerr << "错误: " << err.message
<< "\n详情: " << err.detail << std::endl;
return 1;
}
return 0;
}
关键设计原则
[*]统一错误接口:所有函数利用类似的错误返回方式(如Result<T>)
[*]错误信息丰富化:携带足够上下文用于调试
[*]资源安全:通过保卫对象模仿RAII
[*]错误处置处罚集中化:在高层级统一处置处罚错误,避免分散的错误查抄
[*]错误链支持:追踪错误根源,类似非常的堆栈回溯
通过这种方式,可以在禁用非常的环境中构建可维护的错误处置处罚体系,同时保持代码的结构化。对于性能关键代码,可进一步优化错误对象的内存分配(如利用预分配错误池)。
5 利用层次化非常处置处罚。
在C++中实现层次化非常处置处罚,需结合体系架构分层设计非常类型和处置处罚逻辑。以下为分层处置处罚战略、代码示例及最佳实践:
1. 分层非常类型设计
1.1 底子非常类(所有非常基类)
#include <string>
#include <exception>
class BaseException : public std::exception {
public:
BaseException(const std::string& message, int code = 0, const BaseException* cause = nullptr)
: msg_(message), code_(code), cause_(cause ? cause->clone() : nullptr) {}
const char* what() const noexcept override { return msg_.c_str(); }
int code() const noexcept { return code_; }
const BaseException* cause() const noexcept { return cause_.get(); }
// 克隆方法用于异常链
virtual BaseException* clone() const {
return new BaseException(*this);
}
protected:
std::string msg_;
int code_;
std::unique_ptr<BaseException> cause_;
};
1.2 分层非常派生类
// 数据访问层异常
class DaoException : public BaseException {
public:
enum ErrorType { CONNECTION_FAILED, QUERY_ERROR };
DaoException(ErrorType type, const std::string& sql, int db_errno)
: BaseException(formatMsg(type, sql, db_errno), db_errno),
sql_(sql), error_type_(type) {}
ErrorType error_type() const { return error_type_; }
const std::string& sql() const { return sql_; }
DaoException* clone() const override {
return new DaoException(*this);
}
private:
static std::string formatMsg(ErrorType type, const std::string& sql, int err) {
return std::string("DAO Error: ") +
(type == CONNECTION_FAILED ? "连接失败" : "查询失败") +
" SQL: " + sql + " Code: " + std::to_string(err);
}
std::string sql_;
ErrorType error_type_;
};
// 业务逻辑层异常
class ServiceException : public BaseException {
public:
ServiceException(const std::string& bizMsg, int bizCode, const BaseException& cause)
: BaseException(bizMsg, bizCode, &cause) {}
ServiceException* clone() const override {
return new ServiceException(*this);
}
};
// 用户界面层异常(最终展示给用户)
class UIException : public BaseException {
public:
UIException(const std::string& userFriendlyMsg)
: BaseException(userFriendlyMsg) {}
};
2. 分层处置处罚战略
2.1 数据访问层(DAO Layer)
[*]职责:捕获数据库原生非常,转换为DaoException
class UserDao {
public:
User findUser(int id) {
try {
executeQuery("SELECT * FROM users WHERE id=" + std::to_string(id));
} catch (const mysqlpp::Exception& e) { // 假设使用MySQL++
throw DaoException(DaoException::QUERY_ERROR,
e.query(), e.errnum());
}
// ...
}
};
2.2 业务逻辑层(Service Layer)
[*]职责:捕获DAO非常,转换为业务语义非常,添加业务上下文
class UserService {
public:
void transferMoney(int from, int to, double amount) {
try {
UserDao dao;
dao.withdraw(from, amount); // 可能抛出DaoException
dao.deposit(to, amount);
} catch (const DaoException& e) {
throw ServiceException("资金转账失败", 1001, e);
} catch (const std::invalid_argument& e) {
// 处理参数错误,不包装直接抛出
throw;
}
}
};
2.3 用户界面层(UI Layer)
[*]职责:捕获所有未处置处罚非常,转换为用户友好提示
void onTransferButtonClicked() {
try {
UserService service;
service.transferMoney(getFromId(), getToId(), getAmount());
showSuccess("转账成功!");
} catch (const ServiceException& e) {
// 解析业务错误码
std::string userMsg = "操作失败: " + std::string(e.what());
if (e.code() == 1001) userMsg += "(请检查余额)";
showError(userMsg);
// 记录详细日志
logError(e);
} catch (const std::exception& e) {
showError("系统错误,请联系管理员");
logError(e);
}
}
// 日志记录函数(递归打印异常链)
void logError(const BaseException& e) {
std::cerr << "ERROR: " << e.what() << " \n";
if (auto cause = e.cause()) {
std::cerr << "Caused by: ";
logError(*cause);
}
}
3. 关键技术实现
3.1 非常链(Chain of Causality)
// 抛出时包装底层异常
try {
dao.update(data);
} catch (const DaoException& e) {
throw ServiceException("更新数据失败", 2001, e);
}
// 日志输出
ERROR: 更新数据失败
Caused by: DAO Error: 查询失败 SQL: UPDATE ... Code: 1062
3.2 资源安全(RAII + 非常安全包管)
class Transaction {
public:
Transaction(Database& db) : db_(db) { db_.begin(); }
~Transaction() { if (!committed_) db_.rollback(); }
void commit() {
db_.commit();
committed_ = true;
}
private:
Database& db_;
bool committed_ = false;
};
void businessOperation() {
Transaction tx(db); // RAII管理事务
// 多个数据库操作...
tx.commit(); // 无异常则提交
}
4. 分层处置处罚决议树
5. 性能优化发起
[*]避免过分包装:仅在跨层边界时包装非常
[*]利用noexcept标记:对不会失败的函数(如简朴getter)利用noexceptstd::string getErrorMessage() const noexcept {
return msg_;
}
[*]错误码替代:对高频调用的底层函数利用错误码返回ErrorCode validateInput(const std::string& input) {
if (input.empty()) return EC_EMPTY_INPUT;
return EC_SUCCESS;
}
6. 单元测试战略
TEST_CASE("DAO层应正确转换数据库异常") {
UserDao dao;
REQUIRE_THROWS_MATCHES(
dao.findUser(-1),
DaoException,
Catch::Predicate<DaoException>([](const DaoException& e) {
return e.error_type() == DaoException::QUERY_ERROR;
})
);
}
TEST_CASE("Service层应包装DAO异常") {
MockUserDao dao; // 模拟抛出DaoException
UserService service(dao);
REQUIRE_THROWS_AS(service.transferMoney(1, 2, 100), ServiceException);
}
总结
通过层次化非常处置处罚设计,可实现:
[*]关注点分离:每层专注处置处罚其职责范围内的错误
[*]信息丰富化:非常链携带完整错误上下文
[*]用户友好:UI层隔离技术细节,展示清晰提示
[*]维护性提升:非常类型与业务语义直接关联
关键原则:底层捕获技术非常,高层处置处罚业务语义,顶层包管用户体验。发起结合静态分析工具(如Clang-Tidy)查抄非常安全,确保设计落地。
6 保持非常处置处罚的各个部分尽量简便。
以下是保持C++非常处置处罚简便高效的核心实践和示例:
1. 非常定义简便化
1.1 优先利用标准非常
#include <stdexcept>
void validate_age(int age) {
if (age < 0) throw std::invalid_argument("年龄不能为负");
if (age > 150) throw std::out_of_range("年龄超过合理范围");
}
1.2 须要时创建最少量的自定义非常
// 仅当需要携带额外数据时创建
class PaymentError : public std::runtime_error {
public:
int amount;// 简洁的额外字段
PaymentError(const std::string& msg, int amt)
: std::runtime_error(msg), amount(amt) {}
};
2. 非常抛出简便化
2.1 快速失败(Fail Fast)
void process_input(const std::string& input) {
if (input.empty()) throw std::invalid_argument("输入为空"); // 首行校验
// 后续逻辑...
}
2.2 利用noexcept标记不抛非常的函数
// 明确告知编译器此函数不会抛出
std::string format_message(int code) noexcept {
return "Error-" + std::to_string(code); // 简单操作,确保不抛异常
}
3. 非常捕获简便化
3.1 按层处置处罚,避免过分捕获
// 数据访问层
try {
db.execute(sql);
} catch (const DatabaseTimeout&) {
throw; // 直接重新抛出给业务层
}
// 业务层
try {
process_order();
} catch (const DatabaseTimeout& e) {
retry_operation(); // 业务重试逻辑
} catch (const std::exception& e) {
log_error(e.what());
throw ServiceUnavailable(); // 转换为业务异常
}
// UI层
try {
start_app();
} catch (const ServiceUnavailable&) {
show_error("服务暂不可用,请稍后重试");
} catch (...) {
show_error("发生未知错误");
}
4. 资源管理简便化
4.1 利用智能指针主动管理
void load_data() {
auto file = std::make_unique<std::ifstream>("data.bin");
if (!*file) throw std::runtime_error("无法打开文件");
// 无需手动关闭,unique_ptr析构自动处理
}
4.2 利用标准容器
void process_items() {
std::vector<Item> items;
items.reserve(1000); // 预先分配减少异常可能性
while (auto item = fetch_item()) {
items.push_back(std::move(item)); // 自动内存管理
}
}
5. 非常安全包管简便化
5.1 基本包管(Basic Guarantee)示例
class Config {
std::map<std::string, std::string> params;
public:
void update(const std::string& key, const std::string& value) {
auto temp = params; // 先复制
temp = value; // 修改副本
params.swap(temp);// 无异常则提交(强保证)
}
};
6. 全局处置处罚简便化
6.1 设置简便的终止处置处罚器
#include <cstdlib>
#include <iostream>
void terminate_handler() {
try {
if (auto ex = std::current_exception()) {
std::rethrow_exception(ex);
}
} catch (const std::exception& e) {
std::cerr << "未捕获异常: " << e.what() << "\n";
} catch (...) {
std::cerr << "未知异常类型\n";
}
std::abort();
}
int main() {
std::set_terminate(terminate_handler);
// 主逻辑...
}
7. 测试简便化
7.1 利用宏简化测试代码
#define REQUIRE_THROWS_MSG(expr, msg) \
REQUIRE_THROWS_WITH(expr, Catch::Matchers::Contains(msg))
TEST_CASE("支付金额校验") {
REQUIRE_THROWS_MSG(process_payment(-100), "金额无效");
REQUIRE_NOTHROW(process_payment(500));
}
简便化原则总结
原则实现方式示例避免过分设计优先利用标准非常类型throw std::invalid_argument资源主动管理利用智能指针和容器std::unique_ptr, vector快速失败在函数开始处验证参数if (input.empty()) throw...分层处置处罚每层只处置处罚自己关心的非常DAO层不处置处罚业务非常减少嵌套利用RAII替代try-catch嵌套用unique_ptr而非手动开释明确函数左券用noexcept标记不抛非常的函数void print() noexcept {...} 通过遵循这些简便化原则,可在保持代码健壮性的同时,明显提升可读性和维护性。核心思想:让非常处置处罚逻辑保持简朴、直接和专注,避免不须要的复杂性。
7 不要试图捕获每个函数的每个非常。
在C++非常处置处罚中,"不要试图捕获每个函数的每个非常"是保持代码健壮性和可维护性的重要原则。以下为详细实践指南和代码示例:
1. 分层处置处罚原则
1.1 非常捕获的黄金法则
// 错误示例:底层函数过度捕获
void lowLevelFunc() {
try {
readFromDevice(); // 物理设备读取
} catch (...) { // ❌ 底层捕获所有异常
log("设备错误");
}
}
// 正确做法:允许异常向上传播
void businessLogic() {
try {
lowLevelFunc();
processData();
} catch (const DeviceException& e) { // ✅ 业务层处理
retryOrAbort(e);
}
}
1.2 各层职责划分
层级处置处罚战略示例操纵底层库函数仅抛出,不处置处罚文件操纵失败抛出io_error业务逻辑层捕获可规复非常,转换业务语义将数据库非常转为业务错误码UI/API层终极捕获,展示友好信息弹窗提示"服务不可用" 2. 资源管理主动化
2.1 利用智能指针避免手动清理
// 无需try-catch的资源管理
void processFile() {
auto file = std::make_unique<std::ifstream>("data.bin"); // RAII
if (!*file) throw FileOpenError();
// 即使后续抛出异常,file析构会自动关闭
parseContent(*file);
}
2.2 事务操纵模板
template <typename Func>
void transactionWrapper(Func op) {
beginTransaction(); // 事务开始
try {
op(); // 业务操作
commit(); // 无异常提交
} catch (...) {
rollback(); // 异常回滚
throw; // 继续传播
}
}
// 使用示例
transactionWrapper([] {
updateAccount(1, -100);// 扣款
updateAccount(2, +100);// 加款
});
3. 非常流传战略
3.1 只处置处罚能解决的非常
// 中间件层:仅处理特定异常
void middleware() {
try {
callDownstreamService();
} catch (const TimeoutException&) { // 只处理超时重试
retry(3);
} // 其他异常继续传播
}
// 调用方
try {
middleware();
} catch (const ServiceException& e) {
showUserError(e); // 最终处理
}
3.2 不可规复错误快速失败
void validateConfig(const Config& cfg) {
if (!cfg.isValid()) {
logFatal("配置损坏,无法启动");
std::terminate(); // ❗立即终止
}
}
4. 非常安全包管
4.1 基本非常安全示例
class Document {
std::vector<Page> pages_;
public:
void addPage(const Page& p) {
pages_.push_back(p); // 可能抛出bad_alloc
// 失败时保持原有pages_不变(基本安全)
}
};
4.2 强非常安全实现
void swapContents(Document& doc, const Page& newPage) {
auto temp = doc.pages_; // 操作副本
temp.push_back(newPage);
doc.pages_.swap(temp);// 无异常则原子交换(强安全)
}
5. 日记与调试辅助
5.1 集中式非常日记
// 全局捕获处理器
void globalHandler() {
try {
// ...重新抛出异常
} catch (const std::exception& e) {
logException(e);
if (isCritical(e)) sendAlert();
}
}
// 结构化日志记录
void logException(const std::exception& e) {
json log = {
{"timestamp", getTime()},
{"type", typeid(e).name()},
{"message", e.what()},
{"stack", getStacktrace()}
};
logger.write(log);
}
关键决议表
场景处置处罚方式代码示例物理资源操纵失败(文件/网络)抛出技术非常,不捕获throw FileIOException(...)业务规则校验失败立即抛出业务非常throw InvalidOrder(...)第三方库错误包装为体系非常向上抛throw DatabaseError(...)内存耗尽等不可规复错误记录后终止程序std::terminate()用户输入校验立即返回错误码,不利用非常return ErrorCode::Invalid 最佳实践总结
[*]抛出清晰:每个非常应携带足够诊断信息(错误码、上下文)
[*]资源无忧:通过RAII和智能指针确保资源主动开释
[*]层级过滤:技术非常在底层抛出,业务层转换语义,UI层友好提示
[*]安全包管:明确函数提供的非常安全品级(基本/强/noexcept)
[*]监控到位:全局处置处罚器记录未捕获非常,结合日记分析
通过遵循这些原则,可在保持代码简便的同时构建健壮的非常处置处罚体系,避免陷入"非常处置处罚反模式"的陷阱。记住:好的非常处置处罚是战略性的,而非事无巨细的战术防御。
8 至少提供基本保障。
在C++中,确保代码至少提供 基本非常安全包管(Basic Exception Safety Guarantee)是健壮代码设计的底线。基本包管要求:当非常发生时,程序不泄露资源,所有对象仍处于有效状态(可安全析构),且程序状态保持一致性。以下是详细实现战略和代码示例:
1. RAII(资源获取即初始化)
1.1 利用智能指针管理动态内存
#include <memory>
void process_data(size_t size) {
auto buffer = std::make_unique<int[]>(size); // 自动管理内存
// 即使后续操作抛出异常,buffer析构时也会自动释放内存
fill_buffer(buffer.get(), size);
save_to_database(buffer.get(), size);
}
1.2 自定义RAII类管理文件句柄
#include <fstream>
class SafeFile {
public:
explicit SafeFile(const std::string& path)
: file_(path, std::ios::binary) {
if (!file_) throw std::runtime_error("无法打开文件");
}
~SafeFile() noexcept {
if (file_.is_open()) file_.close();
}
// 禁用拷贝,允许移动
SafeFile(const SafeFile&) = delete;
SafeFile& operator=(const SafeFile&) = delete;
SafeFile(SafeFile&&) = default;
SafeFile& operator=(SafeFile&&) = default;
void write(const std::string& data) {
file_ << data;
if (!file_.good()) throw std::runtime_error("写入失败");
}
private:
std::ofstream file_;
};
// 使用示例
void log_message(const std::string& msg) {
SafeFile logfile("app.log"); // RAII保证文件关闭
logfile.write(msg);
}
2. 非常安全的关键操纵
2.1 构造函数中的非常安全
class DatabaseConnection {
public:
DatabaseConnection(const std::string& config) {
handle_ = open_connection(config); // 可能失败的操作
if (!handle_) {
throw std::runtime_error("连接失败");
}
// 若此处抛出异常,已分配的handle_会被析构函数释放
}
~DatabaseConnection() noexcept {
if (handle_) close_connection(handle_);
}
private:
DBHandle* handle_ = nullptr;
};
2.2 赋值操纵符的非常安全
class Config {
public:
Config& operator=(const Config& other) {
if (this != &other) {
auto temp = other.data_;// 先复制数据
data_.swap(temp); // 无异常则交换(强保证)
}
return *this;
}
private:
std::vector<std::string> data_;
};
3. 避免资源走漏的编码模式
3.1 确保先分配资源再修改状态
void update_user_profile(User& user, const Profile& new_profile) {
auto* new_data = new ProfileData(new_profile); // 先分配资源
delete user.data_; // 再释放旧资源
user.data_ = new_data;// 最后更新指针
}
3.2 利用std::lock_guard管理互斥锁
#include <mutex>
std::mutex db_mutex;
void thread_safe_query() {
std::lock_guard<std::mutex> lock(db_mutex); // 自动释放锁
execute_query("SELECT * FROM users"); // 可能抛出异常
}
4. 非常安全的数据结构操纵
4.1 利用std::vector取代裸数组
class SensorData {
public:
void add_reading(double value) {
readings_.push_back(value); // 可能抛出bad_alloc
// 若push_back失败,readings_仍保持原有数据
}
private:
std::vector<double> readings_; // 自动管理内存
};
4.2 安全的元素删除(先复制后修改)
void remove_invalid_entries(std::vector<Entry>& entries) {
auto valid_entries = entries; // 先创建副本
std::erase_if(valid_entries, [](const Entry& e) {
return !e.is_valid();
});
entries.swap(valid_entries);// 无异常则原子交换
}
5. 测试非常安全
5.1 验证资源开释
TEST_CASE("数据库连接异常时释放资源") {
bool resource_released = false;
try {
DatabaseConnection conn("invalid_config"); // 构造失败
} catch (...) {
resource_released = check_resource_cleanup(); // 验证句柄已关闭
}
REQUIRE(resource_released);
}
5.2 验证数据一致性
TEST_CASE("vector插入失败时保持原数据") {
std::vector<int> data = {1, 2, 3};
auto original = data;
try {
data.insert(data.end(), 4); // 假设插入失败抛出异常
} catch (...) {
REQUIRE(data == original); // 数据未损坏
}
}
关键原则总结
原则实现方法示例资源主动开释利用RAII对象(智能指针、容器、自定义类)std::unique_ptr, SafeFile操纵序次安全先分配新资源,再开释旧资源new_data → delete old无副作用修改先操纵副本,成功后再修改主数据temp.swap(data_)原子性操纵利用swap或move实现无非常操纵vector::swap避免裸资源管理优先利用标准库容器而非new/deletestd::vector取代int[] 通过遵循这些实践,纵然代码中发生非常,也能确保:
[*]资源不走漏:所有已分配的内存、文件句柄、网络毗连等都会被正确开释
[*]对象有效:所有对象处于可安全析构的状态
[*]数据一致性:关键数据结构不会处于半完成状态
终极发起:在类的设计文档中明确标注每个方法的非常安全品级(基本/强/无非常),作为与团队约定的左券。
9 除非有足够的理由,否则最好提供强保障。
在C++中,强非常安全包管(Strong Exception Safety Guarantee) 要求操纵要么完全成功,要么失败时程序状态与操纵开始前完全一致。以下是如何实现这一目的的战略与代码示例:
1. 强非常安全的核心模式
1.1 Copy-and-Swap 模式
class Config {
public:
void update(const std::string& key, const std::string& value) {
auto temp = data_; // 1. 创建副本
temp = value; // 2. 修改副本(可能抛异常)
data_.swap(temp); // 3. 无异常则原子交换
}
private:
std::map<std::string, std::string> data_;
};
1.2 事务性文件写入
void safe_write_file(const std::string& path, const std::string& content) {
const std::string temp_path = path + ".tmp";
{ // 临时文件作用域
std::ofstream tmp(temp_path);
if (!tmp) throw FileOpenError(temp_path);
tmp << content; // 可能抛异常
} // 文件流在此析构,确保内容刷新到磁盘
if (std::rename(temp_path.c_str(), path.c_str()) != 0) {
throw FileRenameError(temp_path, path);
}
}
2. 标准库的强安全操纵
2.1 std::vector 的插入操纵
std::vector<int> data = {1, 2, 3};
// 强安全保证的插入方式
data.reserve(data.size() + 1); // 预先分配空间(可能抛bad_alloc)
data.push_back(4); // 不会重新分配,保证强安全
2.2 std::map 的安全更新
std::map<int, std::string> registry;
// 安全插入或更新
auto hint = registry.find(42);
if (hint != registry.end()) {
auto temp = hint->second;// 创建副本
temp += "_modified"; // 修改副本
registry = std::move(temp); // 原子替换
} else {
registry.emplace(42, "new_value"); // 无副作用的插入
}
3. 移动语义优化
3.1 移动+回滚机制
class Transaction {
public:
Transaction() {
backup_ = current_state_; // 保存初始状态
}
void commit() {
// 尝试应用修改(可能抛异常)
apply_changes();
committed_ = true;
}
~Transaction() {
if (!committed_) {
current_state_ = backup_; // 失败时回滚
}
}
private:
static State current_state_;
State backup_;
bool committed_ = false;
};
// 使用示例
void business_operation() {
Transaction tx;// 进入作用域即开始事务
tx.modify_A(); // 修改操作
tx.modify_B();
tx.commit(); // 无异常则提交
} // 析构时自动处理回滚
4. 性能衡量场景
4.1 可担当的妥协示例
// 场景:高频调用的低延迟函数
void process_packet(NetworkPacket& packet) noexcept {
// 禁用异常,使用错误码返回
if (!validate(packet)) {
stats_.invalid_packets++; // 基本保证:计数器可能少计
return;
}
// 处理逻辑...
}
妥协理由:
[*]每秒处置处罚百万级网络包
[*]错误率低于0.1%
[*]计数器精度损失可担当
5. 强安全测试战略
5.1 利用std::exception_ptr模仿失败
TEST_CASE("数据库更新满足强安全保证") {
Database original = get_current_state();
bool exception_thrown = false;
try {
// 模拟可能失败的操作
throw_on_nth_call(2); // 第2次调用抛异常
perform_atomic_update();
} catch (...) {
exception_thrown = true;
}
REQUIRE(exception_thrown);
REQUIRE(get_current_state() == original); // 验证状态回滚
}
实现强安全的关键技术
技术适用场景示例Copy-and-Swap对象状态修改vector::push_back事务模式多步调操纵数据库事务无副操纵简朴查询/只读方法const成员函数移动语义资源所有权转移std::unique_ptr的移动构造延迟修改必要原子性的批量操纵先收集所有修改,最后一次性提交 何时可以放宽要求?
场景可担当的安全品级理由性能关键路径基本包管强安全开销凌驾性能预算临时对象操纵无包管对象即将销毁,状态无关紧要错误统计基本包管计数器误差可担当硬件交互无包管物理设备状态无法回滚 最佳实践总结
[*]默认选择强安全:除非有明确理由否则优先实现
[*]利用标准库办法:vector::push_back、map::emplace等
[*]事务封装:对复杂操纵利用RAII事务包装器
[*]移动优于拷贝:用移动语义减少临时对象创建
[*]明确文档左券:在函数注释中声明安全品级
通过遵循这些原则,可以在大多数场景下以公道成本实现强非常安全,构建出既健壮又高效的C++体系。
10 让构造函数建立不变式,假如不能,则抛出非常。
在C++中,构造函数是建立类不变式(class invariants)的理想位置。假如构造函数无法满意类的不变式要求,应当抛出非常以确保对象不会被部分构造或处于无效状态。以下是详细实现战略和代码示例:
1. 基本设计原则
[*]不变式定义:明确类在任何时间必须满意的条件(如0 < month <= 12)。
[*]构造函数验证:在构造函数中强制查抄不变式条件。
[*]非常抛出:若不变式无法建立,立即抛出非常制止对象创建。
[*]资源安全:利用RAII确保已分配资源在抛出非常前主动开释。
2. 标准示例:日期类
#include <stdexcept>
#include <string>
class Date {
public:
// 构造函数必须建立"有效日期"的不变式
Date(int year, int month, int day)
: year_(year), month_(month), day_(day)
{
if (!is_valid(year, month, day)) {
throw std::invalid_argument("无效的日期");
}
}
private:
int year_, month_, day_;
static bool is_valid(int y, int m, int d) {
if (m < 1 || m > 12) return false;
if (d < 1 || d > days_in_month(y, m)) return false;
return true;
}
static int days_in_month(int y, int m) { /* ... */ }
};
// 使用示例
try {
Date birthday(2023, 2, 30); // 抛出异常:2月无30日
} catch (const std::invalid_argument& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
3. 复杂场景:资源管理类
3.1 文件句柄管理
#include <fstream>
#include <memory>
class SafeFile {
public:
explicit SafeFile(const std::string& path)
: file_(std::make_unique<std::ifstream>(path))
{
if (!file_->is_open()) {
throw std::runtime_error("无法打开文件: " + path);
}
// 其他初始化(如读取文件头验证)
validate_header();
}
private:
std::unique_ptr<std::ifstream> file_;
void validate_header() {
char header;
file_->read(header, 4);
if (!is_valid_header(header)) {
throw std::runtime_error("文件头不合法");
}
}
};
// 使用示例
try {
SafeFile config("settings.dat"); // 可能抛出两种异常
} catch (const std::exception& e) {
// 文件打开失败或头验证失败
}
3.2 数据库毗连池
#include <vector>
#include <memory>
class DatabaseConnection { /* ... */ };
class ConnectionPool {
public:
ConnectionPool(size_t pool_size, const std::string& conn_str)
: connections_()
{
if (pool_size == 0) {
throw std::invalid_argument("连接池大小必须>0");
}
try {
for (size_t i = 0; i < pool_size; ++i) {
connections_.push_back(
std::make_unique<DatabaseConnection>(conn_str)
);
}
} catch (const DatabaseException&) {
// 部分连接已创建,但构造函数失败
// unique_ptr自动释放已分配连接
throw; // 重新抛出
}
}
private:
std::vector<std::unique_ptr<DatabaseConnection>> connections_;
};
4. 高级本领:复合对象构造
4.1 成员对象的非常安全初始化
class UserProfile {
public:
UserProfile(const std::string& name, int age)
: name_(validate_name(name)),// 可能抛异常
age_(validate_age(age)), // 可能抛异常
preferences_(load_prefs()) // 可能抛异常
{
// 所有成员已成功初始化才进入构造函数体
}
private:
std::string name_;
int age_;
Preferences preferences_;
static std::string validate_name(const std::string& name) {
if (name.empty()) throw std::invalid_argument("名字不能为空");
return name;
}
static int validate_age(int age) {
if (age < 0) throw std::invalid_argument("年龄不能为负");
return age;
}
static Preferences load_prefs() { /* 可能抛异常 */ }
};
5. 错误处置处罚最佳实践
场景处置处罚战略代码示例参数无效抛出std::invalid_argumentthrow std::invalid_argument(...)资源分配失败抛出std::runtime_error或其派生类throw std::runtime_error(...)依赖项初始化失败答应非常流传,RAII主动清理不捕获,依赖析构函数开释资源多阶段验证在成员初始化列表中尽早失败: member(validate(arg)) 关键原则总结
[*]失败前置:在构造函数体实验前验证参数(利用成员初始化列表)
[*]原子性构造:要么完全成功,要么抛出非常(无半构造对象)
[*]资源自治:每个成员自身管理资源(利用智能指针、容器等)
[*]非常类型明确:根据错误类型选择标准或自定义非常
[*]克制吞非常:构造函数不应捕获无关非常
通过遵循这些原则,可以确保类的实例始终处于有效状态,同时保持代码的健壮性和可维护性。
11 抛出非常前先开释局部资源。
在C++中,确保在抛出非常前开释局部资源的最佳实践是接纳RAII(Resource Acquisition Is Initialization)机制。以下是分步说明和代码示例:
1. 明白RAII原则
[*]核心思想:将资源(内存、文件句柄、锁等)的生命周期绑定到对象的生命周期。
[*]构造函数:获取资源。
[*]析构函数:开释资源,确保在对象销毁时主动开释。
2. 利用智能指针管理动态内存
示例:避免内存走漏
#include <memory>
#include <vector>
void process_data() {
std::unique_ptr<std::vector<int>> data = std::make_unique<std::vector<int>>(1000);
// 操作数据,可能抛出异常
data->push_back(42);
// 无需手动释放内存,unique_ptr析构时自动释放
}
3. 封装文件句柄的RAII类
示例:安全处置处罚文件
#include <cstdio>
class FileRAII {
public:
FileRAII(const char* filename, const char* mode) : file_(fopen(filename, mode)) {
if (!file_) throw std::runtime_error("无法打开文件");
}
~FileRAII() noexcept {
if (file_) fclose(file_);
}
FILE* handle() const { return file_; }
// 禁用拷贝
FileRAII(const FileRAII&) = delete;
FileRAII& operator=(const FileRAII&) = delete;
private:
FILE* file_;
};
void read_file() {
FileRAII file("data.txt", "r"); // 文件打开成功
// 读取文件操作,若抛出异常,析构函数自动关闭文件
char buffer;
fread(buffer, 1, sizeof(buffer), file.handle());
}
4. 利用标准库容器管理资源
示例:主动开释动态数组
#include <vector>
void safe_array_operation() {
std::vector<int> array(1000); // 自动管理内存
array = 42; // 操作可能抛出异常
// 无需手动释放,vector析构时自动释放内存
}
5. 锁管理的RAII实现
示例:避免死锁
#include <mutex>
std::mutex critical_section_mutex;
void thread_safe_operation() {
std::lock_guard<std::mutex> lock(critical_section_mutex); // 自动加锁
// 临界区操作,可能抛出异常
// 析构时自动解锁,即使发生异常
}
6. 自定义数据库毗连的RAII类
示例:安全开释数据库毗连
class DatabaseConnection {
public:
DatabaseConnection(const std::string& conn_str) {
connect(conn_str); // 可能抛出连接异常
}
~DatabaseConnection() noexcept {
if (connected_) disconnect(); // 确保关闭连接
}
void query(const std::string& sql) { /* 可能抛出查询异常 */ }
private:
bool connected_ = false;
void connect(const std::string& str) { /* ... */ }
void disconnect() noexcept { /* ... */ }
};
void use_database() {
DatabaseConnection db("user=admin;password=1234"); // 连接成功
db.query("SELECT * FROM users"); // 可能抛出异常
// 析构时自动断开连接
}
7. 手动资源管理的正确方式(不推荐)
示例:在try块内开释资源
void legacy_code() {
Resource* res = acquire_resource(); // 获取资源
try {
use_resource(res); // 可能抛出异常
} catch (...) {
release_resource(res); // 异常时释放资源
throw; // 重新抛出
}
release_resource(res); // 正常流程释放
}
关键原则总结
场景RAII解决方案上风动态内存std::unique_ptr, std::vector主动开释内存,避免走漏文件操纵自定义FileRAII类或std::fstream非常安全地关闭文件句柄多线程锁std::lock_guard主动解锁,防止死锁网络/数据库毗连封装毗连的RAII类确保毗连终极关闭第三方C库资源自定义RAII包装器将C风格资源转换为非常安全对象 为何RAII优于手动开释?
[*]非常安全:纵然抛出非常,析构函数仍会调用。
[*]代码简便:消除重复的try-catch和delete/free调用。
[*]维护性强:资源生命周期清晰可见。
[*]性能优化:减少冗余错误查抄代码。
验证RAII的非常安全性
#include <cassert>
class TestRAII {
public:
TestRAII(bool fail) : valid_(true) {
if (fail) throw std::runtime_error("构造失败");
}
~TestRAII() { valid_ = false; }
bool is_valid() const { return valid_; }
private:
bool valid_;
};
int main() {
try {
TestRAII obj(true); // 构造时抛出异常
} catch (const std::exception&) {
// 对象未被完全构造,析构函数不会执行
}
TestRAII normal_obj(false);
assert(normal_obj.is_valid()); // 正常构造
return 0; // 析构函数调用,valid_变为false
}
通过遵循RAII原则,您可以确保所有局部资源在非常发生时被正确开释,从而编写出健壮且易于维护的C++代码。
12 服膺在构造函数中抛出非常前开释所有已获取的资源。
在C++中,当构造函数抛出非常时,已完全构造的成员变量和基类子对象的析构函数会被调用,但当前对象的析构函数不会实验。因此,必须通过RAII机制确保资源主动开释。以下是关键原则和代码示例:
1. 基本原则:分层获取资源
1.1 成员变量按声明序次初始化
class DatabaseConnection {
public:
// RAII成员按声明顺序初始化
DatabaseConnection(const std::string& conn_str)
: logger_("db.log"), // 1. 先初始化日志文件(RAII)
handle_(connect(conn_str)) // 2. 再获取数据库连接
{
if (!handle_) {
// ❌ 错误:此时logger_已初始化,无法阻止其析构函数调用
throw std::runtime_error("连接失败");
}
}
private:
FileRAII logger_;// RAII成员,自动管理文件资源
DBHandle* handle_; // ❌ 危险:裸指针需手动释放
};
修正方案:所有资源由RAII成员管理
class DatabaseConnection {
public:
DatabaseConnection(const std::string& conn_str)
: logger_("db.log"),
handle_(make_connection(conn_str)) // handle_是unique_ptr
{
if (!handle_) {
// ✅ 无需手动释放,handle_析构函数自动处理
throw std::runtime_error("连接失败");
}
}
private:
FileRAII logger_;
std::unique_ptr<DBHandle> handle_; // RAII管理数据库连接
};
2. 分步资源获取战略
2.1 每个资源对应一个RAII成员
class SecureSession {
public:
SecureSession(const std::string& user)
: auth_token_(authenticate(user)), // RAII成员1:令牌
encryptor_(init_encryption()),// RAII成员2:加密器
log_stream_("session.log") // RAII成员3:日志文件
{
// 所有资源通过成员初始化列表获取
// 任一成员构造失败都会导致已构造成员的析构
}
private:
AuthTokenRAII auth_token_;
EncryptionRAII encryptor_;
FileRAII log_stream_;
};
2.2 动态资源管理
class ImageProcessor {
public:
ImageProcessor(int width, int height)
: buffer1_(std::make_unique<uint8_t[]>(width * height)),
buffer2_(std::make_unique<uint8_t[]>(width * height))
{
if (!validate_buffers()) {
throw std::runtime_error("缓冲区初始化失败");
}
// ✅ 异常安全:unique_ptr自动释放内存
}
private:
std::unique_ptr<uint8_t[]> buffer1_;
std::unique_ptr<uint8_t[]> buffer2_;
};
3. 非常安全构造函数模板
3.1 正确模式
class SafeResourceOwner {
public:
SafeResourceOwner()
: res1_(acquire_resource_1()),// RAII成员1
res2_(acquire_resource_2()) // RAII成员2
{
// 仅在所有资源就绪后进行额外操作
perform_initialization();
}
private:
ResourceRAII res1_;
ResourceRAII res2_;
};
3.2 错误模式(手动管理)
class DangerousResourceOwner {
public:
DangerousResourceOwner() {
res1_ = new Resource(); // ❌ 裸指针
if (!res1_->ok()) {
delete res1_; // ⚠️ 必须手动释放
throw std::runtime_error("资源1失败");
}
res2_ = new Resource(); // ❌ 第二个资源
if (!res2_->ok()) {
delete res1_; // ⚠️ 需要手动释放res1_
delete res2_;
throw std::runtime_error("资源2失败");
}
}
~DangerousResourceOwner() {
delete res1_;
delete res2_;
}
private:
Resource* res1_;
Resource* res2_;
};
4. 无法利用RAII时的处置处罚
4.1 手动清理(不推荐,应急用)
class LegacyWrapper {
public:
LegacyWrapper() {
handle1_ = legacy_init_1(); // C风格API
if (!handle1_) {
legacy_cleanup_1(handle1_); // 手动清理
throw std::runtime_error("初始化1失败");
}
handle2_ = legacy_init_2();
if (!handle2_) {
legacy_cleanup_1(handle1_); // 清理已分配资源
legacy_cleanup_2(handle2_);
throw std::runtime_error("初始化2失败");
}
}
~LegacyWrapper() {
legacy_cleanup_2(handle2_);
legacy_cleanup_1(handle1_);
}
private:
LegacyHandle handle1_;
LegacyHandle handle2_;
};
关键原则总结
场景正确做法错误做法成员资源初始化通过RAII成员在初始化列表中获取资源在构造函数体内用new分配多资源依赖每个资源对应一个RAII成员,按依赖序次声明手动管理多个资源的开释序次第三方库集成编写RAII包装类封装C风格资源直接利用裸指针或句柄非常处置处罚依赖析构函数主动清理try-catch块内手动delete 验证工具
// 使用valgrind检测资源泄漏
valgrind --leak-check=full ./your_program
// 编译时开启额外警告
g++ -Wall -Wextra -Werror your_code.cpp
通过严格遵循RAII原则,可以确保纵然构造函数抛出非常,所有已获取的资源也能被安全开释,避免内存走漏和资源滞留。
13 假如局部控制结构足以满意要求,不要利用非常。
在C++中,非常机制应仅用于处置处罚 不可规复的错误 或 跨多层调用传递错误。对于可通过局部逻辑处置处罚的常规错误,优先利用控制结构(如if-else、while)而非非常。以下是详细场景和最佳实践:
1. 优先利用控制结构的场景
1.1 参数校验(可预期错误)
// ✅ 正确:使用if直接处理
std::optional<int> safe_divide(int a, int b) {
if (b == 0) return std::nullopt; // 预期内的错误
return a / b;
}
// ❌ 错误:滥用异常
int unsafe_divide(int a, int b) {
if (b == 0) throw std::invalid_argument("除零错误");
return a / b;
}
1.2 用户输入验证
// ✅ 正确:通过循环和条件重试
int read_positive_number() {
int num;
while (true) {
std::cout << "输入正整数: ";
if (std::cin >> num && num > 0) return num;
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "输入无效,请重试\n";
}
}
// ❌ 错误:输入错误时抛出异常(过度设计)
int bad_read_number() {
int num;
if (!(std::cin >> num)) throw std::runtime_error("输入错误");
return num;
}
2. 错误码与状态标记
2.1 返回错误码
enum class ErrorCode { Success, FileNotFound, InvalidData };
ErrorCode process_file(const std::string& path) {
if (!file_exists(path)) return ErrorCode::FileNotFound;
// 处理文件内容...
return ErrorCode::Success;
}
// 调用方处理
if (auto code = process_file("data.txt"); code != ErrorCode::Success) {
log_error(code);
}
2.2 利用std::expected(C++23)
#include <expected>
std::expected<std::string, ErrorCode> load_config(const std::string& path) {
if (!file_exists(path)) return std::unexpected(ErrorCode::FileNotFound);
// 读取配置...
return config_data;
}
// 调用方
if (auto config = load_config("app.cfg"); config) {
use_config(*config);
} else {
handle_error(config.error());
}
3. 资源管理(无需非常)
3.1 RAII主动开释
class FileHandler {
public:
FileHandler(const std::string& path) : file_(fopen(path.c_str(), "r")) {
if (!file_) log_error("打开文件失败"); // 记录但不抛异常
}
~FileHandler() { if (file_) fclose(file_); }
explicit operator bool() const { return file_ != nullptr; }
private:
FILE* file_;
};
void process() {
FileHandler file("data.txt");
if (!file) return; // 提前退出
// 使用文件...
}
4. 性能敏感场景
4.1 高频调用函数
// ✅ 正确:避免异常开销
bool validate_packet(const Packet& pkt) {
if (pkt.size > MAX_SIZE) return false;
if (pkt.checksum != calculate_checksum(pkt)) return false;
return true;
}
// ❌ 错误:异常不适用于高频路径
void bad_validate(const Packet& pkt) {
if (pkt.size > MAX_SIZE) throw InvalidPacket();
if (pkt.checksum != calculate_checksum(pkt)) throw InvalidPacket();
}
5. 替代非常的常用模式
场景替代方案工具/本领可规复错误返回错误码或bool状态std::optional, std::expected可选值缺失std::optional<T>C++17标准库多错误类型std::variant<T, Error>C++17联合类型输入验证循环+条件重试while + std::cin.fail()资源不可用RAII类+状态查抄自定义资源管理类 何时应利用非常?
场景示例理由内存耗尽new失败程序无法继承运行硬件故障磁盘写入失败无法通过局部逻辑规复第三方库瓦解数据库毗连突然断开跨层级错误传递不可规复的逻辑错误程序不变量被粉碎必要立即终止或重启 代码可维护性对比
非常滥用代码
try {
auto data = parse_input();
save_to_db(data);
} catch (const InvalidInput&) {
log("输入错误");
} catch (const DatabaseTimeout&) {
retry();
} catch (...) {
log("未知错误");
}
控制结构优化代码
if (auto input = read_input(); input.valid()) {
if (auto conn = db.connect(); conn.active()) {
conn.save(input.data());
} else {
handle_db_error(conn.status());
}
} else {
ask_for_retry();
}
总结:选择错误处置处罚战略的决议树
错误是否可预期且频繁发生?
├── 是 → 使用控制结构(if/返回码)
└── 否 → 错误是否会导致程序无法继续?
├── 是 → 抛出异常
└── 否 → 错误是否需要跨多层传递?
├── 是 → 使用异常
└── 否 → 使用局部处理
通过公道选择错误处置处罚战略,可明显提升代码性能和可维护性。记住:非常应作为“最后防线”,而非常规控制流工具。
14 用“资源获取即初始化”技术管理资源。
在C++中,“资源获取即初始化”(RAII, Resource Acquisition Is Initialization)是管理资源的黄金准则。以下是分步调的详细说明和代码示例:
1. RAII核心思想
将 资源生命周期 与 对象生命周期 绑定:
[*]构造函数:获取资源
[*]析构函数:开释资源
[*]无论程序流程如何(包罗非常),资源都能主动开释
2. 基本实现模板
class RAIIWrapper {
public:
// 构造函数获取资源
explicit RAIIWrapper(ResourceParams params)
: resource_(acquire_resource(params))
{
if (!resource_) throw ResourceAcquisitionFailed();
}
// 析构函数释放资源
~RAIIWrapper() noexcept {
if (resource_) release_resource(resource_);
}
// 禁用拷贝(根据需要实现移动语义)
RAIIWrapper(const RAIIWrapper&) = delete;
RAIIWrapper& operator=(const RAIIWrapper&) = delete;
// 可选:提供资源访问接口
ResourceHandle get() const noexcept { return resource_; }
private:
ResourceHandle resource_; // 资源句柄(指针、文件描述符等)
};
3. 范例应用场景
3.1 管理动态内存
// 使用标准库智能指针(无需自定义)
void process_data(size_t size) {
auto buffer = std::make_unique<int[]>(size); // RAII自动管理
// 使用buffer...
// 无需手动delete,离开作用域自动释放
}
3.2 管理文件句柄
class FileRAII {
public:
explicit FileRAII(const std::string& path, const char* mode = "r")
: file_(fopen(path.c_str(), mode))
{
if (!file_) throw std::runtime_error("无法打开文件");
}
~FileRAII() noexcept {
if (file_) fclose(file_);
}
FILE* handle() const noexcept { return file_; }
// 启用移动语义
FileRAII(FileRAII&& other) noexcept
: file_(std::exchange(other.file_, nullptr)) {}
FileRAII& operator=(FileRAII&& other) noexcept {
if (this != &other) {
if (file_) fclose(file_);
file_ = std::exchange(other.file_, nullptr);
}
return *this;
}
private:
FILE* file_;
};
// 使用示例
void read_file() {
FileRAII file("data.txt");
char buffer;
fread(buffer, 1, sizeof(buffer), file.handle());
// 文件自动关闭
}
3.3 管理互斥锁
#include <mutex>
class CriticalSection {
public:
void safe_operation() {
std::lock_guard<std::mutex> lock(mutex_); // RAII自动加锁
// 临界区操作...
// 离开作用域自动解锁
}
private:
std::mutex mutex_;
};
4. 高级应用本领
4.1 延迟初始化
class LazyResource {
public:
void initialize() {
if (!resource_) {
resource_ = acquire_resource();
if (!resource_) throw std::bad_alloc();
}
}
~LazyResource() {
if (resource_) release_resource(resource_);
}
private:
ResourceHandle resource_ = nullptr;
};
4.2 组合RAII对象
class DatabaseTransaction {
public:
DatabaseTransaction(Database& db)
: conn_(db.acquire_connection()), // RAII成员1:连接
lock_(conn_.mutex()) // RAII成员2:锁
{
conn_.begin_transaction();
}
~DatabaseTransaction() {
if (std::uncaught_exceptions() > 0) {
conn_.rollback();
} else {
conn_.commit();
}
}
private:
DatabaseConnectionRAII conn_;
std::lock_guard<std::mutex> lock_;
};
5. 标准库RAII工具
资源类型标准库工具头文件动态内存std::unique_ptr, std::shared_ptr<memory>文件流std::fstream, std::ofstream<fstream>互斥锁std::lock_guard, std::unique_lock<mutex>线程std::jthread (C++20)<thread>临时文件/目录std::filesystem::directory_entry<filesystem> 6. 非常安全包管
品级形貌实现方法基本包管资源不走漏,对象状态有效析构函数正确开释资源强包管操纵要么完全成功,要么无影响Copy-and-Swap + RAII无抛出包管操纵不会抛出非常noexcept + 简朴操纵 7. 验证RAII正确性
// 测试用例:验证资源释放
TEST_CASE("FileRAII自动关闭文件") {
FILE* raw_handle = nullptr;
{
FileRAII file("test.txt", "w");
raw_handle = file.handle();
REQUIRE(fileno(raw_handle) != -1); // 文件已打开
} // RAII对象析构
REQUIRE(fclose(raw_handle) == EOF); // 文件已关闭,再次关闭失败
}
总结
通过RAII技术:
[*]消除手动资源管理:避免new/delete、open/close的配对问题
[*]包管非常安全:纵然发生非常,资源也能正确开释
[*]提升代码可读性:资源生命周期清晰可见
[*]支持更复杂的资源战略:通过组合RAII对象实现事务操纵
发起将RAII作为C++资源管理的默认方式,仅在极少数必要手动控制的场景(如性能关键路径)才思量其他方案。
15 尽量减少利用try块。
在C++中,资源获取即初始化(RAII) 是管理资源(如内存、文件句柄、网络毗连等)的核心技术,通过将资源生命周期与对象生命周期绑定,确保资源主动开释。以下是详细实现战略和代码示例:
1. RAII 核心原则
[*]构造函数获取资源:资源在对象构造时初始化。
[*]析构函数开释资源:对象销毁时主动开释资源。
[*]非常安全:纵然程序抛出非常,资源也能正确开释。
2. 底子模板:自定义RAII类
2.1 管理文件句柄
#include <cstdio>
class FileRAII {
public:
// 构造函数打开文件
explicit FileRAII(const char* path, const char* mode = "r")
: file_(fopen(path, mode)) {
if (!file_) throw std::runtime_error("无法打开文件");
}
// 析构函数关闭文件
~FileRAII() noexcept {
if (file_) fclose(file_);
}
// 禁用拷贝,允许移动
FileRAII(const FileRAII&) = delete;
FileRAII& operator=(const FileRAII&) = delete;
FileRAII(FileRAII&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
// 提供资源访问接口
FILE* handle() const noexcept { return file_; }
private:
FILE* file_;
};
// 使用示例
void read_file() {
FileRAII file("data.txt"); // 打开文件
char buffer;
fread(buffer, 1, sizeof(buffer), file.handle());
// 离开作用域时,file析构自动关闭文件
}
2.2 管理动态数组
template <typename T>
class DynamicArrayRAII {
public:
explicit DynamicArrayRAII(size_t size)
: data_(new T), size_(size) {}
~DynamicArrayRAII() noexcept { delete[] data_; }
T& operator[](size_t index) { return data_; }
private:
T* data_;
size_t size_;
};
// 使用示例
void process_data() {
DynamicArrayRAII<int> arr(1000); // 分配内存
arr = 42;
// 离开作用域时内存自动释放
}
3. 标准库RAII工具
3.1 智能指针管理内存
#include <memory>
void safe_memory_management() {
auto ptr = std::make_unique<int[]>(1000); // unique_ptr自动释放
auto shared = std::make_shared<Resource>(); // shared_ptr引用计数
}
3.2 互斥锁管理(多线程安全)
#include <mutex>
class ThreadSafeCounter {
public:
void increment() {
std::lock_guard<std::mutex> lock(mutex_); // 自动加锁/解锁
++count_;
}
private:
int count_ = 0;
std::mutex mutex_;
};
4. 高级应用场景
4.1 管理数据库毗连
class DatabaseSession {
public:
explicit DatabaseSession(const std::string& conn_str) {
session_ = connect(conn_str); // 可能抛异常
if (!session_.active()) throw std::runtime_error("连接失败");
}
~DatabaseSession() {
if (session_.active()) disconnect(session_);
}
void query(const std::string& sql) { /* ... */ }
private:
DatabaseHandle session_;
};
// 使用示例
void run_query() {
DatabaseSession db("user=admin;password=1234");
db.query("SELECT * FROM users");
// 离开作用域时自动断开连接
}
4.2 组合RAII对象(事务操纵)
class Transaction {
public:
Transaction(Database& db)
: conn_(db.acquire_connection()), // RAII成员1:连接
lock_(conn_.mutex()) // RAII成员2:锁
{
conn_.begin();
}
~Transaction() {
if (std::uncaught_exceptions() > 0) conn_.rollback();
else conn_.commit();
}
private:
DatabaseConnectionRAII conn_;
std::lock_guard<std::mutex> lock_;
};
5. RAII设计原则
原则实现方式示例资源获取即初始化构造函数中获取资源FileRAII构造函数打开文件资源开释即析构析构函数中开释资源(标记为noexcept)~FileRAII()关闭文件禁用拷贝,支持移动删除拷贝构造函数,实现移动语义FileRAII(FileRAII&&)非常安全构造函数失败时抛出非常,析构函数确保开释资源构造函数中throw,析构中fclose提供访问接口通过成员函数暴露资源handle()返回文件指针 6. 常见问题解决
6.1 构造函数中部分资源初始化失败
class MultiResource {
public:
MultiResource()
: res1_(acquire_resource_1()), // 若此处失败,无资源需释放
res2_(acquire_resource_2())// 若此处失败,res1_的析构函数自动释放
{}
private:
ResourceRAII res1_;
ResourceRAII res2_;
};
6.2 延迟资源初始化
class LazyResource {
public:
void initialize() {
if (!resource_) {
resource_ = acquire_resource(); // 延迟获取资源
if (!resource_) throw std::bad_alloc();
}
}
~LazyResource() { if (resource_) release_resource(resource_); }
private:
ResourceHandle resource_ = nullptr;
};
7. RAII与非常安全品级
安全品级形貌RAII实现方式基本非常安全资源不走漏,对象状态有效析构函数正确开释资源强非常安全操纵要么完全成功,要么无副作用Copy-and-Swap + RAII成员无抛出包管操纵绝不抛非常noexcept + 简朴操纵 总结
通过RAII技术,可以:
[*]消除手动资源管理:避免new/delete、open/close的配对错误。
[*]包管非常安全:纵然发生非常,资源也能正确开释。
[*]提升代码可读性:资源生命周期与对象作用域一致。
[*]支持复杂资源战略:通过组合RAII对象实现事务、延迟初始化等高级功能。
推荐实践:
[*]优先利用标准库RAII工具(如智能指针、lock_guard)。
[*]对第三方库资源(如OpenGL纹理、数据库毗连)封装自定义RAII类。
[*]在构造函数中完成所有资源初始化,避免部分初始化状态。
16 并非所有程序都必要非常安全。
在C++开发中,确实存在不必要追求非常安全性的场景。以下是分层说明和详细实践指南:
一、不必要非常安全的范例场景
1. 性能敏感型代码
// 高频交易系统的核心循环
void process_market_data(Packet& pkt) noexcept {
if (pkt.size != VALID_SIZE) return;// 直接返回错误码
calculate_indicators(pkt.data); // 禁用异常开销
send_to_engine(pkt); // 要求绝对低延迟
}
特点:
[*]利用noexcept标记函数
[*]错误处置处罚通过返回值或状态位完成
[*]牺牲错误信息丰富性换取纳秒级性能
2. 嵌入式及时体系
// 电机控制固件
void control_motor(RPM target) {
uint16_t current = read_sensor();// 无异常支持的硬件操作
if (current > MAX_SAFE_VALUE) {
emergency_shutdown(); // 直接终止异常状态
return ERROR_CODE;
}
adjust_pwm(target); // 确定性时序要求
}
特点:
[*]编译器禁用非常(-fno-exceptions)
[*]通过LED闪耀或看门狗复位处置处罚严重错误
[*]错误处置处罚路径必须包管在微秒级完成
3. 简朴下令行工具
// 一次性数据处理工具
int main(int argc, char** argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <file>\n", argv);
return EXIT_FAILURE;// 直接退出无需恢复状态
}
process_file(argv); // 单次执行无需回滚
}
特点:
[*]错误直接导致程序终止
[*]无恒久运行状态必要维护
[*]资源管理依赖操纵体系主动接纳
二、非非常安全代码编写规范
1. 资源管理战略
// C风格手动管理(需严格配对)
void legacy_image_processing() {
uint8_t* buffer = malloc(1024*1024);
if (!buffer) return;
FILE* fp = fopen("data.raw", "rb");
if (!fp) {
free(buffer);// 必须手动释放
return;
}
process(buffer, fp);
fclose(fp);
free(buffer);
}
要点:
[*]每个malloc必须有对应的free
[*]每个open必须有对应的close
[*]错误路径需手工回滚资源分配
2. 错误码传递规范
enum Error { SUCCESS, FILE_ERROR, NETWORK_ERROR };
Error download_file(const char* url) {
CURL* curl = curl_easy_init();
if (!curl) return NETWORK_ERROR;
FILE* fp = fopen("temp.tmp", "wb");
if (!fp) {
curl_easy_cleanup(curl);// 手动清理
return FILE_ERROR;
}
//...传输操作...
fclose(fp);
curl_easy_cleanup(curl);
return SUCCESS;
}
要点:
[*]定义清晰的错误码罗列
[*]每个函数必须返回实验状态
[*]调用方需查抄所有返回值
3. 断言辅助调试
// 快速失败策略用于开发阶段
void unsafe_optimization(float* data) {
assert(data != nullptr && "空指针传递给算法核心");// 调试期检查
simd_processing(data);// Release模式可能产生未定义行为
}
特点:
[*]NDEBUG发布版主动移除查抄
[*]仅用于捕捉开发阶段明显错误
[*]不处置处罚运行时不可猜测错误
三、非常安全与RAII的替代方案
1. 作用域退出主动实验
#include <experimental/scope>
void modern_without_exceptions() {
FILE* fp = fopen("data.txt", "r");
auto file_guard = std::experimental::make_scope_exit([&]{
if (fp) fclose(fp);
});
process_content(fp);// 无论是否出错都会关闭文件
}
上风:
[*]类似RAII的局部资源管理
[*]无需定义完整包装类
[*]支持C风格资源管理
2. 状态标记对象
class NetworkController {
public:
bool connect(const std::string& ip) {
if (!hardware_ready_) return false;// 状态检查
//...物理层操作...
return true;
}
void disconnect() noexcept {
if (connected_) release_port();
}
private:
bool hardware_ready_ = false;
bool connected_ = false;
};
上风:
[*]对象始终保持有效状态
[*]方法返回操纵是否成功
[*]析构函数包管底子清理
四、禁用非常的工程配置
1. 编译器参数设置
# GCC/Clang禁用异常
g++ -fno-exceptions -stdlib=libc++ app.cpp
# MSVC禁用异常(需配合宏)
cl /EHsc- app.cpp
2. 标准库举动调解
// 重载operator new为不抛出版本
void* operator new(size_t size) {
if (auto ptr = malloc(size)) return ptr;
std::abort();// 内存耗尽直接终止
}
// 使用无异常容器
std::vector<int, non_throw_allocator<int>> safe_vec;
五、决议流程图
总结
当开发一次性工具、及时体系、性能关键型组件时,可放弃非常安全机制,通过:
[*]严格配对手工资源管理
[*]清晰错误码传递体系
[*]断言辅助调试
[*]作用域保卫工具
来构建可靠体系。但需在项目文档中明确标注非非常安全模块,限定其利用范围。
17 用“资源获取即初始化”技术和非常处置处罚程序维护不变式。
在C++中,通过 资源获取即初始化(RAII) 结合 非常处置处罚,可以有效地维护类的不变式(Class Invariants),确保对象始终处于有效状态。以下是详细实现战略和代码示例:
1. 核心原则
[*]构造函数建立不变式:若无法满意不变式,立即抛出非常。
[*]析构函数开释资源:确保对象销毁时清理资源。
[*]成员变量RAII化:所有资源由RAII对象管理,避免手动清理。
2. 底子示例:文件管理类
#include <fstream>
#include <stdexcept>
class SafeFile {
public:
// 构造函数:尝试打开文件,失败则抛出异常
explicit SafeFile(const std::string& path)
: file_(path, std::ios::binary)
{
if (!file_.is_open()) {
throw std::runtime_error("无法打开文件: " + path);
}
validate_header(); // 可能抛出自定义异常
}
// 析构函数:自动关闭文件(无需手动操作)
~SafeFile() noexcept = default;
// 成员函数:确保操作后仍满足不变式
void write(const std::string& data) {
std::string temp = encrypt(data); // 可能抛异常
file_ << temp;
if (file_.fail()) {
throw std::runtime_error("写入失败");
}
}
private:
std::ofstream file_;
void validate_header() {
char header;
file_.read(header, sizeof(header));
if (!is_valid(header)) {
throw std::invalid_argument("无效文件头");
}
}
};
3. 复合对象:数据库事务
#include <memory>
#include <vector>
class DatabaseTransaction {
public:
// 构造函数:按顺序初始化RAII成员
DatabaseTransaction(const std::string& conn_str)
: logger_("transaction.log"), // RAII成员1:日志文件
conn_(connect(conn_str)), // RAII成员2:数据库连接
lock_(conn_.mutex()) // RAII成员3:互斥锁
{
if (!conn_.is_active()) {
throw std::runtime_error("数据库连接失败");
}
conn_.begin(); // 开启事务
}
// 析构函数:根据事务成功与否提交或回滚
~DatabaseTransaction() noexcept {
try {
if (std::uncaught_exceptions() > 0 || !committed_) {
conn_.rollback();
}
} catch (...) {} // 确保析构函数不抛出
}
void commit() {
validate_operations(); // 可能抛异常
conn_.commit();
committed_ = true;
}
void execute(const std::string& sql) {
conn_.execute(sql); // 可能抛异常
operations_.push_back(sql);
}
private:
FileRAII logger_; // 日志文件RAII管理
DatabaseConnection conn_;// 数据库连接RAII对象
std::unique_lock<std::mutex> lock_; // 锁RAII管理
std::vector<std::string> operations_;
bool committed_ = false;
};
4. 强非常安全包管:Copy-and-Swap
class ConfigManager {
public:
// 修改配置(强异常安全保证)
void update(const std::string& key, const std::string& value) {
auto temp = data_; // 1. 创建副本
temp = value; // 2. 修改副本(可能抛异常)
data_.swap(temp); // 3. 无异常则原子交换
}
private:
std::map<std::string, std::string> data_;
};
5. 维护不变式的关键点
5.1 构造函数中的非常处置处罚
class TemperatureSensor {
public:
explicit TemperatureSensor(int id)
: handle_(init_sensor(id)) // RAII成员初始化
{
if (get_current_temp() < ABSOLUTE_ZERO) { // 违反不变式
throw std::logic_error("传感器数据异常");
}
}
private:
SensorHandleRAII handle_; // 传感器资源由RAII管理
};
5.2 成员函数的非常安全
class Account {
public:
void transfer(Account& to, double amount) {
if (amount <= 0 || balance_ < amount) {
throw std::invalid_argument("无效金额");
}
auto temp_from = balance_ - amount; // 不直接修改成员
auto temp_to = to.balance_ + amount;
balance_ = temp_from;// 无异常则提交修改
to.balance_ = temp_to;
}
private:
double balance_ = 0.0;
};
6. 非常处置处罚与资源开释
6.1 多层调用中的资源传递
void process_data() {
SafeFile input("input.dat"); // RAII对象1
SafeFile output("output.dat"); // RAII对象2
try {
auto data = parse(input); // 可能抛异常
transform(data);
output.write(data);
} catch (const ParseError& e) {
log_error("解析失败: ", e.what());
throw; // 重新抛出,input/output自动关闭
}
}
7. 设计原则总结
原则实现方法示例构造函数建立不变式若初始化失败,立即抛出非常throw std::runtime_error(...)析构函数开释资源所有资源由RAII成员管理,析构函数标记为noexcept~FileRAII() noexcept成员函数强非常安全利用Copy-and-Swap或事务模式temp.swap(data_)资源获取序次按声明序次初始化RAII成员logger_先于conn_初始化非常流传仅在能处置处罚的层级捕获非常UI层捕获并显示错误 错误处置处罚决议树
是否违反类不变式?
├── 是 → 抛出异常阻止对象创建
└── 否 → 操作是否可能失败?
├── 是 → 使用RAII + 异常传递
└── 否 → 直接执行操作
通过结合RAII和非常处置处罚,可以确保:
[*]资源安全:无论是否发生非常,资源都会被正确开释。
[*]状态一致性:对象始终保持有效状态。
[*]代码简便性:消除冗余的错误查抄代码。
关键准则:每个类的设计都应明确其不变式,并通过RAII和非常处置处罚在构造函数、成员函数中强制维护这些不变式。
18 资源句柄优于弱结构化的finally。
在C++中,利用 资源句柄(RAII) 相比类似finally的弱结构化清理机制具有明显上风,尤其在资源管理、代码安全性和可维护性方面。以下是关键对比和详细示例:
1. 作用域控制的天然上风
资源句柄(RAII)示例
void process_file() {
std::ifstream file("data.txt"); // 资源句柄:文件打开即初始化
parse(file); // 使用资源
} // 此处自动调用~ifstream()关闭文件
类finally结构示例(伪代码)
void unsafe_process() {
FILE* fp = fopen("data.txt", "r");
try {
parse(fp);
} finally { // 虚构语法
fclose(fp); // 需要显式清理
}
}
核心差异:
[*]RAII资源生命周期与代码块作用域 严格绑定
[*]finally必要 手动指定 清理代码位置,易遗漏或误用
2. 多资源管理复杂度对比
RAII主动处置处罚资源依赖
void secure_operation() {
std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx);// 资源1:互斥锁
std::ofstream log("audit.log"); // 资源2:日志文件
db_operation(); // 可能抛异常
} // 自动先释放log,再释放lock(逆初始化顺序)
finally结构的繁琐管理
void error_prone_operation() {
Mutex mtx;
FileHandle log;
try {
mtx.lock();
log = open("audit.log");
db_operation();
} finally {
log.close(); // 需注意释放顺序
mtx.unlock(); // 与加锁顺序相反
}
}
问题暴露:
[*]finally块中需 手动维护资源开释序次(与获取序次相反)
[*]新增资源时需修改多处代码,易堕落
3. 非常安全性的根本差异
RAII在非常时的举动
void raii_example() {
ResourceA a; // 构造成功
ResourceB b; // 构造失败,抛出异常
} // a的析构函数自动调用,资源释放
finally在非常时的陷阱
void finally_example() {
ResourceA* a = new ResourceA();
try {
ResourceB* b = new ResourceB(); // 抛异常
} finally {
delete a; // 执行,但b未被释放(未进入try块)
}
}
关键缺陷:
[*]finally无法处置处罚 构造函数内抛出的非常(如ResourceB构造失败)
[*]RAII在 任何控制流退出路径(包罗非常)都能触发析构
4. 代码可维护性对比
RAII的自我文档化
class EncryptedConnection {
public:
EncryptedConnection(const Endpoint& ep)
: socket_(ep.address),// 先建立socket
cipher_(generate_key()) // 再初始化加密
{} // 明确资源初始化顺序
private:
TcpSocket socket_; // 资源1:网络连接
CipherContext cipher_; // 资源2:加密上下文
};
finally的隐式依赖
void connect() {
Socket s = new Socket();
try {
Cipher c = new Cipher();
try {
s.connect(endpoint);
c.init(s.getKey());
} finally {
c.release(); // 需了解c依赖s
}
} finally {
s.close();
}
}
维护成本:
[*]finally要求开发者 显式影象资源依赖关系
[*]修改资源初始化序次需重构多个finally块
5. 性能与编译器优化
RAII的零开销抽象
// 编译后代码(伪汇编):
process_file:
call std::ifstream constructor
call parse
call std::ifstream destructor
ret
finally的运行时成本
// 伪代码:finally实现需要额外状态跟踪
void function() {
register_cleanup(cleanup_code); // 运行时注册清理函数
// ...主逻辑...
execute_cleanup(); // 退出时执行
}
性能差异:
[*]RAII依赖编译时生成的析构函数调用,无运行时开销
[*]finally通常必要 运行时栈记录清理操纵,影响性能
6. 复杂控制流中的可靠性
RAII处置处罚提前返回
void early_return_example() {
AuditLog log("ops.log"); // 构造即管理
if (!check_permission()) return; // 自动清理log
critical_operation();
} // log在此处析构
finally需覆盖所有出口
void finally_risky() {
File f = openFile();
try {
if (!checkPerm()) return; // 直接返回,跳过finally
// ...操作...
} finally {
f.close(); // 仅在try块退出时执行
}
// 此处f未关闭!
}
弊端分析:
[*]finally仅处置处罚 try块退出,无法捕获函数中其他返回路径
[*]RAII覆盖 所有可能的控制流路径
总结:资源句柄的核心上风
特性RAII资源句柄finally结构作用域管理主动绑定生命周期需手动指定清理位置多资源序次依赖声明序次,主动逆序析构需手动维护开释序次非常安全性构造函数失败仍能清理已初始化的资源无法处置处罚try块外的构造函数非常代码简便性资源获取即管理,无需显式清理每个资源需配套try-finally块性能零运行时开销需额外状态跟踪维护成本低(新增资源不影响现有代码)高(需修改多个finally块) 结论:在C++中,RAII资源句柄通过
[*]作用域绑定
[*]主动析构序次
[*]非常安全包管
[*]零开销抽象
成为资源管理的首选方案,远优于必要手动维护的finally模式。对于从其他语言转向C++的开发者,明白并应用RAII是编写工业级健壮代码的关键。
19 为你的不变式设计错误处置处罚战略。
在C++中,为类的不变式(Class Invariants)设计错误处置处罚战略必要结合 非常处置处罚 和 RAII技术,确保对象始终处于有效状态。以下是分层的错误处置处罚方案和详细实现示例:
一、不变式验证的核心战略
1. 构造函数强制校验
原则:构造函数必须完成所有不变式的校验,失败时抛出非常,制止创建无效对象。
class TemperatureSensor {
public:
explicit TemperatureSensor(int id)
: handle_(init_sensor(id))// RAII管理硬件资源
{
// 校验传感器初始读数是否符合不变式
float initial = read_temperature();
if (initial < -273.15f) {
throw std::logic_error("传感器读数违反物理定律");
}
}
private:
SensorHandle handle_;// RAII成员,自动释放资源
};
2. 成员函数的强非常安全
原则:修改对象状态的操纵必须包管 强非常安全(要么成功,要么不影响原状态)。
class BankAccount {
public:
void transfer(BankAccount& to, double amount) {
// 1. 校验不变式:金额必须非负且足够
if (amount <= 0 || balance_ < amount) {
throw std::invalid_argument("无效转账金额");
}
// 2. 操作副本,避免直接修改成员变量
double new_from = balance_ - amount;
double new_to = to.balance_ + amount;
// 3. 无异常则提交修改(原子操作)
balance_ = new_from;
to.balance_ = new_to;
}
private:
double balance_ = 0.0;
};
二、错误处置处罚分层设计
1. 底层(资源层)
[*]目的:确保资源正确获取和开释。
[*]战略:利用RAII类封装资源,构造函数失败时抛出非常。
class DatabaseConnection {
public:
explicit DatabaseConnection(const std::string& conn_str)
: conn_handle_(connect(conn_str))// 可能抛异常
{
if (!conn_handle_.active()) {
throw std::runtime_error("数据库连接失败");
}
}
// 析构函数自动断开连接(noexcept)
~DatabaseConnection() noexcept {
if (conn_handle_.active()) disconnect(conn_handle_);
}
private:
ConnHandle conn_handle_;// RAII资源句柄
};
2. 中间层(业务逻辑)
[*]目的:转换技术非常为业务语义非常,添加上下文信息。
[*]战略:捕获底层非常,包装后重新抛出。
class OrderProcessor {
public:
void process_order(const Order& order) {
try {
DatabaseConnection db("user=admin;password=1234");
db.execute(order.to_sql());
} catch (const DatabaseException& e) {
// 添加业务上下文后重新抛出
throw OrderException("订单处理失败: " + order.id(), e);
}
}
};
3. 顶层(UI/API层)
[*]目的:终极处置处罚非常,展示友好信息或记录日记。
[*]战略:集中捕获所有未处置处罚非常,确保程序优雅降级。
int main() {
try {
OrderProcessor processor;
processor.run();
} catch (const OrderException& e) {
std::cerr << "业务错误: " << e.what() << std::endl;
log_error(e);
return 1;
} catch (const std::exception& e) {
std::cerr << "系统错误: " << e.what() << std::endl;
log_critical(e);
return 2;
}
}
三、进阶错误处置处罚技术
1. 自定义非常类
用途:携带更多错误上下文(错误码、时间戳、业务ID等)。
class NetworkException : public std::runtime_error {
public:
NetworkException(const std::string& msg, int error_code, std::string url)
: std::runtime_error(msg),
error_code_(error_code),
url_(std::move(url)),
timestamp_(std::chrono::system_clock::now()) {}
int code() const noexcept { return error_code_; }
const std::string& url() const { return url_; }
std::time_t time() const {
return std::chrono::system_clock::to_time_t(timestamp_);
}
private:
int error_code_;
std::string url_;
std::chrono::system_clock::time_point timestamp_;
};
2. 不变式规复机制
场景:在关键体系中尝试主动规复(如重新毗连硬件)。
class RoboticArmController {
public:
void move_to(Position target) {
try {
internal_move(target);
} catch (const HardwareException& e) {
if (attempt_recovery()) {// 尝试恢复不变式
internal_move(target); // 重试操作
} else {
throw;// 恢复失败,传递异常
}
}
}
private:
bool attempt_recovery() {
reset_driver();// 尝试重置硬件驱动
return calibrate();// 返回是否恢复成功
}
};
四、错误处置处罚决议树
五、最佳实践总结
原则实现方式示例构造函数严格校验失败时抛出非常,制止无效对象生成throw std::invalid_argument成员函数强安全先操纵副本,再原子提交修改temp.swap(data_)非常信息丰富化自定义非常类携带错误上下文NetworkException资源主动开释RAII成员管理资源,析构函数标记为noexcept~DatabaseConnection() noexcept分层处置处罚技术非常在底层抛出,业务层转换语义catch (DBException&) 转 OrderException 通过遵循这些战略,可以构建出 高鲁棒性 的C++体系,确保不变式被粉碎时程序举动可猜测且安全。
20 能在编译时查抄的东西最幸亏编译时查抄(利用static_assert)。
在C++中,利用 static_assert 在编译时进行条件查抄,可以提前捕获潜在错误,提升代码健壮性并消除运行时开销。以下是关键应用场景和代码示例:
一、底子类型束缚
1. 验证类型大小
确保类型满意特定内存结构要求:
// 必须为4字节类型(如int32_t)
struct PacketHeader {
uint32_t type;
uint32_t length;
static_assert(sizeof(PacketHeader) == 8, "PacketHeader大小必须为8字节");
};
2. 查抄平台兼容性
确保类型在差别平台上的表现一致:
// 验证指针大小为8字节(64位系统)
static_assert(sizeof(void*) == 8, "仅支持64位架构");
二、模板元编程束缚
1. 限定模板参数类型
确保模板参数为整数类型:
template <typename T>
class IntegerWrapper {
static_assert(std::is_integral_v<T>, "T必须是整数类型");
public:
T value;
};
// 合法使用
IntegerWrapper<int> iw_ok{42};
// 编译错误:类型不匹配
IntegerWrapper<float> iw_err{3.14f};
2. 验证模板参数关系
确保模板参数之间有正确的继承关系:
template <typename Base, typename Derived>
void safe_cast(Derived& d) {
static_assert(std::is_base_of_v<Base, Derived>,
"Derived必须继承自Base");
// 安全转换逻辑...
}
三、常量表达式验证
1. 数组大小合法性
编译时验证数组维度:
constexpr int MatrixSize = 16;
class Matrix {
float data;
static_assert(MatrixSize > 0 && (MatrixSize & (MatrixSize - 1)) == 0,
"矩阵大小必须是2的幂");
};
2. 罗列值范围查抄
确保罗列值在有效范围内:
enum class Color : uint8_t { Red = 1, Green = 2, Blue = 3 };
template <Color C>
struct ColorInfo {
static_assert(C >= Color::Red && C <= Color::Blue,
"无效的颜色值");
// 颜色处理逻辑...
};
四、自定义类型特性查抄
1. 验证特定接口存在
利用SFINAE或C++20概念束缚:
// 检查类型是否有serialize方法
template <typename T>
struct has_serialize {
template <typename U>
static auto test(U*) -> decltype(std::declval<U>().serialize(), std::true_type{});
static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test((T*)nullptr))::value;
};
template <typename T>
void save(const T& obj) {
static_assert(has_serialize<T>::value, "T必须实现serialize方法");
obj.serialize();
}
2. 确保移动语义正确性
验证类型是否为可移动构造:
template <typename T>
class OptimizedContainer {
static_assert(std::is_move_constructible_v<T>,
"T必须支持移动构造");
// 使用移动语义优化存储...
};
五、配置参数校验
1. 编译时配置文件验证
确保配置参数合法:
constexpr int MaxConnections = 100;
constexpr int CurrentConnections = 150;
// 检查连接数是否超限
static_assert(CurrentConnections <= MaxConnections,
"当前连接数超过最大限制");
2. 算法参数束缚
验证算法参数的数学有效性:
template <int N>
struct Factorial {
static_assert(N >= 0, "阶乘参数必须非负");
static constexpr int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> { static constexpr int value = 1; };
六、错误处置处罚战略对比
查抄类型编译时查抄 (static_assert)运行时查抄 (assert或非常)触发机遇编译阶段程序运行时性能影响无额外开销可能引入分支判断和非常处置处罚开销错误定位直接定位源码位置需调试或日记分析适用场景类型特性、常量表达式、模板参数等确定性问题用户输入、文件状态等动态不确定性问题 七、最佳实践总结
[*]优先编译时查抄:对类型、常量表达式等确定性问题利用static_assert
[*]结合类型特性库:利用<type_traits>中的工具简化束缚条件
[*]明确错误信息:提供清晰的静态断言消息,方便开发者定位问题
[*]避免滥用:动态条件(如用户输入)仍需运行时查抄
通过将尽可能多的查抄移至编译时,可以明显提升代码质量和运行效率,减少调试成本。
21 用你的错误处置处罚战略实验差别层级的查抄。
在C++中,通过分层错误处置处罚战略可以在差别层级实验针对性查抄,确保体系的健壮性和可维护性。以下为分层的错误处置处罚方案及代码示例:
一、分层错误处置处罚战略
层级查抄目的技术本领错误处置处罚方式编译时类型安全、静态束缚static_assert、概念束缚编译失败,制止生成可实验文件数据访问层资源有效性、技术非常(如数据库毗连)RAII + 自定义非常类抛出技术非常业务逻辑层业务规则有效性(如订单状态)防御性编程 + 业务非常捕获技术非常,抛出业务非常UI/API层输入合法性、展示友好错误参数校验 + 全局非常处置处罚器转换非常为HTTP状态码/弹窗 二、编译时查抄(静态束缚)
1. 验证模板参数合法性
template <typename T>
class Vector3D {
static_assert(std::is_arithmetic_v<T>,
"Vector3D元素类型必须是算术类型");
public:
T x, y, z;
};
// 合法使用
Vector3D<float> v1;
// 编译错误:static_assert失败
Vector3D<std::string> v2;
2. 确保平台特性
// 验证是否为小端序架构
static_assert(std::endian::native == std::endian::little,
"本系统仅支持小端序架构");
三、数据访问层(RAII + 技术非常)
1. 数据库毗连管理
class DatabaseConnection {
public:
explicit DatabaseConnection(const std::string& conn_str)
: handle_(connect(conn_str)) // RAII初始化
{
if (!handle_.active()) {
throw DatabaseException("连接失败", conn_str, errno);
}
}
void execute(const std::string& sql) {
if (auto code = handle_.query(sql); code != 0) {
throw QueryException("查询失败", sql, code);
}
}
private:
DBHandleRAII handle_; // RAII管理连接生命周期
};
2. 文件操纵非常
void parse_config(const std::string& path) {
SafeFile file(path); // RAII自动开/关文件
if (!file.validate_signature()) {
throw FileFormatException("无效文件签名", path);
}
// 解析操作...
}
四、业务逻辑层(业务非常转换)
1. 订单处置处罚
class OrderProcessor {
public:
void process(const Order& order) {
try {
check_inventory(order); // 可能抛DatabaseException
deduct_stock(order);
create_shipping(order);
} catch (const DatabaseException& e) {
// 添加业务上下文后重新抛出
throw OrderProcessingException("订单处理失败", order.id(), e);
}
}
private:
void check_inventory(const Order& order) {
if (order.quantity() <= 0) {
throw InvalidOrderException("订单数量无效", order.id());
}
}
};
2. 付出网关调用
class PaymentService {
public:
Receipt charge(CreditCard card, double amount) {
if (amount <= 0) {
throw InvalidAmountException("金额必须为正数", amount);
}
try {
return gateway_.charge(card, amount); // 可能抛NetworkException
} catch (const NetworkException& e) {
throw PaymentFailedException("支付网关错误", e.details());
}
}
};
五、UI/API层(全局非常处置处罚)
1. REST API错误处置处罚
// 全局异常处理器(基于Crow框架示例)
CROW_ROUTE(app, "/api/order")([](const crow::request& req){
try {
Order order = parse_order(req.body);
OrderProcessor().process(order);
return crow::response(200);
} catch (const OrderProcessingException& e) {
// 业务异常:返回4xx状态码
return crow::response(400, json{{"error", e.what()}});
} catch (const std::exception& e) {
// 系统异常:记录日志,返回5xx
log_critical(e.what());
return crow::response(500);
}
});
2. GUI应用错误提示
void MainWindow::onSubmitOrder() {
try {
Order order = ui_gather_input();
order_processor_.submit(order);
show_success("订单提交成功");
} catch (const InvalidInputException& e) {
show_error("输入错误", e.field_hint());
} catch (const PaymentFailedException& e) {
show_retry_dialog(e.retry_advice());
} catch (...) {
show_fatal_error("系统发生未知错误");
}
}
六、错误处置处罚工具链
工具/技术适用层级用途static_assert编译时强制类型/平台束缚自定义非常体系所有运行时层传递丰富错误上下文RAII包装类数据访问层主动资源清理全局非常捕获UI/API层防止瓦解,统一错误格式化日记体系所有层错误追踪和审计 七、分层战略上风
[*]关注点分离:每层仅处置处罚职责内的错误类型
[*]错误信息丰富化:非常在传递过程中携带更多上下文
[*]资源安全:RAII确保任何层级抛出非常都不会走漏资源
[*]用户体验优化:顶层将技术细节转换为友好提示
[*]维护性提升:编译时查抄减少运行时错误可能性
通过这种分层战略,可以构建出既健壮又易于维护的C++体系,各层级错误处置处罚职责明确,且能有效协作。
22 假如函数不会抛出非常,把它声明成noexcept的。
在C++中,假如一个函数确实不会抛出任何非常,将其声明为noexcept具有多重上风,但也需谨慎评估其适用性。以下是分步调的总结和发起:
1. 确定函数是否真正不会抛出非常
[*]查抄所有内部操纵:确认函数内部调用的所有函数、操纵符和构造函数均不会抛出非常。
[*]底子类型操纵(如算术运算)通常不会抛出非常。
[*]动态内存分配(new)、标准库容器操纵(如std::vector::push_back)可能抛出std::bad_alloc等非常。
[*]文件、网络等I/O操纵可能因外部因素失败并抛出非常。
[*]处置处罚潜在非常:若函数内部可能抛出非常,但已通过try-catch块完全捕获并处置处罚,确保无非常流传到函数外。
2. 声明函数为noexcept
[*]语法示例:void safe_function() noexcept {
// 确保此处代码不会抛出异常
}
[*]移动构造函数/赋值运算符:class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// 移动资源,保证不抛出异常
}
MyClass& operator=(MyClass&& other) noexcept {
// 移动赋值,保证不抛出异常
return *this;
}
};
[*]标准库(如std::vector)会优先利用noexcept移动操纵提升性能。
3. 性能与优化
[*]编译器优化:noexcept答应编译器省略非常处置处罚机制(如栈展开表),生成更高效的代码。
[*]标准库优化:
[*]容器操纵(如std::vector扩容)在元素类型支持noexcept移动时,优先利用移动而非复制。
[*]算法(如std::sort)可能因noexcept得到性能提升。
4. 接口设计与文档
[*]明确左券:noexcept是函数接口的一部分,告知调用者无需预备非常处置处罚。
[*]维护束缚:一旦声明noexcept,后续修改必须维持其不抛出非常的承诺,否则导致未定义举动。
5. 特别情况处置处罚
[*]虚函数:若基类虚函数声明为noexcept,所有派生类重写版本也必须保持noexcept,否则编译错误。class Base {
public:
virtual void func() noexcept = 0;
};
class Derived : public Base {
public:
void func() noexcept override { /* ... */ }
};
[*]条件性noexcept:在模板中,根据类型特性动态决定是否noexcept。template<typename T>
void swap(T& a, T& b) noexcept(noexcept(T(std::move(a))) && noexcept(a = std::move(b))) {
// 根据T的移动操作是否noexcept决定自身异常规范
}
6. 风险与注意事项
[*]错误利用结果:若函数实际可能抛出非常却被声明为noexcept,非常触发时直接调用std::terminate()终止程序。
[*]维护成本:修改函数实现时,需重新验证noexcept的适用性,避免粉碎左券。
7. 推荐实践
[*]简朴函数:如纯算术运算、获取器(getters)等明显无非常的函数,应声明noexcept。
[*]移动操纵:移动构造函数和赋值运算符应尽量实现为noexcept。
[*]避免滥用:对于可能抛出非常或涉及外部资源的函数,谨慎评估后再决定。
[*]静态分析工具:利用Clang-Tidy等工具查抄noexcept的误用。
示例对比
未利用noexcept:
void process_data() {
std::vector<int> data;
data.reserve(1000); // 可能抛出std::bad_alloc
// ...其他操作...
}
利用noexcept(错误示例,可能导致瓦解):
void process_data() noexcept {
std::vector<int> data;
data.reserve(1000); // 若抛出异常,程序直接终止
}
正确做法(处置处罚非常或避免noexcept):
void process_data() {
try {
std::vector<int> data;
data.reserve(1000);
} catch (const std::bad_alloc& e) {
// 处理内存不足,如降级方案
}
}
总结
[*]优先声明:对明确无非常的函数(如简朴盘算、移动操纵)利用noexcept,提升性能和代码质量。
[*]谨慎评估:涉及资源操纵的函数需确保非常被完全处置处罚,避免误用noexcept导致程序不稳固。
[*]团队规范:统一项目的noexcept利用标准,结合代码审查和静态分析确保正确性。
23 不要利用非常说明。
在C++中,动态非常规范(比方 void func() throw(int);)已在C++11中被标记为废弃,并在C++17中完全移除。以下是避免利用非常说明的详细缘故原由及现取代代方案:
一、为什么不利用非常说明?
1. 运行时查抄而非编译时
[*]问题:动态非常规范在运行时验证,若函数抛出未声明的非常类型,会触发 std::unexpected(),导致程序终止。
[*]示例:void func() throw(int) {
throw "error"; // 抛出const char*,但未在声明中列出
}
[*]编译通过,但运行时会瓦解。
2. 性能开销
[*]问题:编译器需生成额外代码来查抄非常类型,纵然未抛出任何非常。
[*]对比:noexcept无运行时开销,仅影响编译优化。
3. 维护成本高
[*]问题:修改函数可能抛出的非常类型时,需手动更新所有相关声明,易堕落。
[*]示例:// 旧声明
void process() throw(FileError);
// 修改后需抛出NetworkError
void process() throw(FileError, NetworkError); // 需手动更新
4. 无法与模板协同
[*]问题:模板函数无法为所有可能的类型指定动态非常规范。
[*]示例:template<typename T>
void swap(T& a, T& b) throw(); // 不现实,因T的拷贝可能抛出异常
二、替代方案:现代C++非常处置处罚战略
1. 利用 noexcept 明确不抛非常
[*]用途:声明函数不会抛出任何非常。
[*]上风:
[*]编译时标记,无运行时开销。
[*]答应编译器优化(如移动语义优化)。
[*]示例:void safe_operation() noexcept {
// 确保此处代码不会抛出异常
}
2. 强非常安全包管
[*]原则:通过RAII和noexcept移动操纵实现强非常安全。
[*]示例:class DataContainer {
public:
DataContainer(DataContainer&& other) noexcept
: data_(std::move(other.data_)) {}
// 强保证:要么完全成功,要么无副作用
void update() {
auto temp = data_;// 先操作副本
temp.modify();
data_.swap(temp); // 无异常则提交
}
private:
std::vector<int> data_;
};
3. 基于左券的编程
[*]工具:利用assert或static_assert在关键位置验证前置/后置条件。
[*]示例:void process(int* ptr) {
assert(ptr != nullptr && "指针不能为空");
// 安全操作ptr
}
4. 错误码 + 结构化返回
[*]场景:性能敏感或禁用非常的环境(如嵌入式体系)。
[*]示例:enum class Error { Success, InvalidInput, Timeout };
std::pair<Result, Error> safe_operation(Input input) {
if (!input.valid()) return { {}, Error::InvalidInput };
// 处理逻辑...
return { result, Error::Success };
}
5. 自定义非常体系
[*]原则:通过继承std::exception定义清晰的非常类型,传递上下文信息。
[*]示例:class NetworkException : public std::runtime_error {
public:
NetworkException(const std::string& url, int code)
: std::runtime_error("Network error: " + url),
error_code_(code) {}
int code() const noexcept { return error_code_; }
private:
int error_code_;
};
三、迁徙旧代码中的非常说明
1. 更换 throw() 为 noexcept
[*]旧代码:void legacy_func() throw(); // C++03风格
[*]新代码:void legacy_func() noexcept; // C++11+风格
2. 删除详细非常类型声明
[*]旧代码:void parse() throw(FileError, FormatError); // 已废弃
[*]新代码:void parse(); // 不限制异常类型,但需在文档中说明可能抛出的异常
3. 利用静态分析工具
[*]工具:Clang-Tidy的 modernize-use-noexcept 查抄项可主动更换throw()为noexcept。
四、总结:最佳实践
场景处置处罚方式工具/技术函数绝不抛非常利用noexcept移动构造函数、简朴工具函数可能抛非常明确非常类型(继承std::exception)自定义非常类 + 文档说明资源管理RAII + 智能指针std::unique_ptr, 自定义RAII类输入校验断言 + 错误码assert, std::optional高性能场景禁用非常 + 错误码编译器选项-fno-exceptions 通过避免过期的非常说明,接纳现代C++的非常处置处罚战略,可以明显提升代码的安全性、性能和可维护性。
24 用引用的方式捕获层次体系中的非常。
在C++中,当处置处罚具有层次结构的非常体系时,通过引用捕获非常(catch (const BaseException& e))是最安全、最机动的方式。这种方式能够保持非常的多态性,避免对象切片(Object Slicing),同时答应高效访问非常信息。以下是详细说明和最佳实践:
1. 非常层次结构设计
定义基类非常
#include <stdexcept>
#include <string>
// 基类异常,继承自标准异常
class BaseException : public std::exception {
public:
BaseException(const std::string& msg, int code = 0)
: msg_(msg), code_(code) {}
const char* what() const noexcept override {
return msg_.c_str();
}
int code() const noexcept { return code_; }
private:
std::string msg_;
int code_;
};
// 派生异常类:文件操作异常
class FileException : public BaseException {
public:
FileException(const std::string& path, int errno_code)
: BaseException("文件错误: " + path, errno_code), path_(path) {}
const std::string& path() const { return path_; }
private:
std::string path_;
};
// 派生异常类:网络异常
class NetworkException : public BaseException {
public:
NetworkException(const std::string& url, int http_status)
: BaseException("网络错误: " + url, http_status), url_(url) {}
const std::string& url() const { return url_; }
private:
std::string url_;
};
2. 抛出非常
在必要的地方抛出详细的派生类非常:
void load_file(const std::string& path) {
if (!file_exists(path)) {
throw FileException(path, ENOENT); // 抛出文件不存在异常
}
// ...其他操作...
}
void fetch_data(const std::string& url) {
if (http_get(url).status != 200) {
throw NetworkException(url, 500); // 抛出网络异常
}
}
3. 通过引用捕获非常
3.1 基本捕获方式
int main() {
try {
load_file("data.txt");
fetch_data("https://example.com");
} catch (const FileException& e) {
// 捕获文件异常
std::cerr << "文件错误: " << e.what()
<< "\n路径: " << e.path()
<< "\n错误码: " << e.code() << std::endl;
} catch (const NetworkException& e) {
// 捕获网络异常
std::cerr << "网络错误: " << e.what()
<< "\nURL: " << e.url()
<< "\nHTTP状态码: " << e.code() << std::endl;
} catch (const BaseException& e) {
// 捕获基类异常(其他派生类)
std::cerr << "基础错误: " << e.what()
<< "\n错误码: " << e.code() << std::endl;
} catch (const std::exception& e) {
// 捕获标准异常
std::cerr << "标准异常: " << e.what() << std::endl;
} catch (...) {
// 捕获所有其他异常
std::cerr << "未知异常" << std::endl;
}
}
3.2 关键上风
[*] 避免对象切片:
假如通过值捕获(catch (BaseException e)),派生类对象会被切割为基类对象,丢失派生类特有数据(如FileException::path_)。
[*] 支持多态访问:
通过引用可以正确调用派生类的虚函数(如what())。
[*] 高效性:
引用捕获避免了拷贝非常对象的开销。
4. 捕获序次与原则
4.1 从详细到一般
捕获序次应从最详细的非常到最通用的非常,确保每个非常类型都能被正确处置处罚:
try {
// ...可能抛出FileException、NetworkException...
} catch (const FileException& e) { // 先捕获具体异常
handle_file_error(e);
} catch (const NetworkException& e) { // 再捕获其他具体异常
handle_network_error(e);
} catch (const BaseException& e) { // 最后捕获基类异常
handle_generic_error(e);
}
4.2 不要忽略基类捕获
纵然所有已知非常都已处置处罚,也应生存基类或std::exception的捕获块,避免未处置处罚的非常导致程序终止:
try {
// ...
} catch (const FileException& e) {
// 处理文件异常
} catch (const BaseException& e) {
// 处理其他自定义异常
} catch (const std::exception& e) {
// 处理标准库异常
} catch (...) {
// 处理未知异常(如第三方库抛出的int、字符串等)
}
5. 动态类型查抄(dynamic_cast)
假如必要根据非常类型实验差别逻辑,可通过dynamic_cast安全转换:
try {
// ...
} catch (const BaseException& e) {
if (auto* file_ex = dynamic_cast<const FileException*>(&e)) {
// 处理文件异常
std::cout << "文件路径: " << file_ex->path() << std::endl;
} else if (auto* net_ex = dynamic_cast<const NetworkException*>(&e)) {
// 处理网络异常
std::cout << "请求URL: " << net_ex->url() << std::endl;
} else {
// 其他基类异常
}
}
6. 最佳实践总结
原则实现方式示例非常设计构建层次化的非常体系,继承自std::exceptionclass FileException : public BaseException抛出非常在得当位置抛出详细的派生类非常throw FileException(path, errno)引用捕获利用const&捕获非常catch (const FileException& e)捕获序次从详细到一般先FileException,后BaseException避免对象切片绝不通过值捕获多态非常不消catch (BaseException e)非常安全结合RAII管理资源std::lock_guard, 智能指针 7. 错误处置处罚决议树
通过引用捕获层次化的非常,结合清晰的非常类型设计,可以构建出既机动又安全的错误处置处罚体系。
25 并非每个非常都派生自exception类。
在C++中,确实存在不继承自std::exception的非常类型。这些非常可能是底子类型(如int、字符串字面量)、自定义类或第三方库定义的非常。为确保程序能正确处置处罚所有可能的非常,需接纳更机动的捕获战略。以下是分层次的解决方案和代码示例:
1. 底子类型非常处置处罚
示例:捕获int错误码
try {
if (error_condition) {
throw 404; // 抛出整型错误码
}
} catch (int code) {
std::cerr << "错误码: " << code << std::endl;
}
示例:捕获const char*字符串
try {
throw "未知错误发生"; // 抛出C风格字符串
} catch (const char* msg) {
std::cerr << "错误信息: " << msg << std::endl;
}
2. 第三方库非常处置处罚
示例:处置处罚第三方库抛出的非常
假设第三方库ThirdPartyLib可能抛出其自定义的LibException类:
try {
ThirdPartyLib::operation(); // 可能抛出LibException
} catch (const ThirdPartyLib::LibException& e) {
// 直接处理第三方异常
std::cerr << "第三方库错误: " << e.what() << std::endl;
} catch (...) {
// 兜底处理其他未知异常
std::cerr << "未知第三方错误" << std::endl;
}
3. 统一非常接口设计
3.1 封装非标准非常
将第三方或底子类型非常封装为继承自std::exception的自定义非常:
class UnifiedException : public std::exception {
public:
UnifiedException(const std::string& msg) : msg_(msg) {}
const char* what() const noexcept override { return msg_.c_str(); }
private:
std::string msg_;
};
void safe_third_party_call() {
try {
ThirdPartyLib::operation();
} catch (const ThirdPartyLib::LibException& e) {
throw UnifiedException("第三方错误: " + std::string(e.what()));
} catch (int code) {
throw UnifiedException("错误码: " + std::to_string(code));
} catch (...) {
throw UnifiedException("未知第三方异常");
}
}
// 使用示例
try {
safe_third_party_call();
} catch (const std::exception& e) {
// 统一处理为std::exception
std::cerr << e.what() << std::endl;
}
3.2 利用std::exception_ptr(C++11+)
跨层传递恣意类型的非常:
std::exception_ptr eptr;
void risky_operation() {
try {
ThirdPartyLib::operation(); // 可能抛出任何类型
} catch (...) {
eptr = std::current_exception(); // 捕获异常指针
}
}
void handle_exception() {
if (eptr) {
try {
std::rethrow_exception(eptr);
} catch (const std::exception& e) {
std::cerr << "标准异常: " << e.what() << std::endl;
} catch (int code) {
std::cerr << "错误码: " << code << std::endl;
} catch (...) {
std::cerr << "未知异常类型" << std::endl;
}
}
}
4. 全面捕获战略
4.1 从详细到一般的catch序次
try {
// 可能抛出多种异常
} catch (const MyCustomException& e) {
// 处理最具体的自定义异常
} catch (const ThirdPartyLib::LibException& e) {
// 处理第三方异常
} catch (const std::exception& e) {
// 处理标准异常
} catch (const char* msg) {
// 处理字符串异常
} catch (int code) {
// 处理整型错误码
} catch (...) {
// 兜底处理其他所有异常
}
4.2 记录未知非常
try {
// ...
} catch (...) {
std::cerr << "发生未知异常,程序将终止" << std::endl;
std::terminate(); // 或执行其他恢复逻辑
}
5. 非常安全与资源管理
结合RAII确保资源安全开释:
class FileRAII {
public:
FileRAII(const std::string& path) : file_(fopen(path.c_str(), "r")) {
if (!file_) throw UnifiedException("无法打开文件");
}
~FileRAII() { if (file_) fclose(file_); }
private:
FILE* file_;
};
void process_file() {
FileRAII file("data.txt"); // 异常安全
// 使用文件...
// 即使抛出异常,文件也会正确关闭
}
总结:处置处罚非标准非常的关键点
场景战略工具/技术底子类型非常直接catch特定类型catch(int), catch(const char*)第三方库非常封装或直接处置处罚自定义包装函数统一非常接口继承std::exception或封装UnifiedException类未知非常处置处罚利用catch(...)兜底结合日记记录和终止逻辑资源安全RAII管理资源智能指针、自定义RAII类 通过机动组合这些战略,可以确保程序正确处置处罚所有类型的非常,同时保持代码的健壮性和可维护性。
26 让main()捕获和报告所有非常。
在C++中,可以通过在main()函数中利用try-catch块来捕获所有非常,并结合分层处置处罚战略确保程序优雅退出。以下是实现方案及代码示例:
1. 底子框架:捕获所有非常类型
#include <iostream>
#include <exception>
#include <cstdlib> // EXIT_FAILURE
int main() {
try {
// 主程序逻辑
return 0;
}
// 1. 优先捕获标准异常体系
catch (const std::exception& e) {
std::cerr << "标准异常: " << e.what() << std::endl;
}
// 2. 捕获字符串异常
catch (const char* msg) {
std::cerr << "C风格异常: " << msg << std::endl;
}
// 3. 捕获整型错误码
catch (int code) {
std::cerr << "错误码: " << code << std::endl;
}
// 4. 兜底捕获其他所有异常
catch (...) {
std::cerr << "未知类型异常" << std::endl;
}
// 异常退出码
return EXIT_FAILURE;
}
2. 分层捕获战略
2.1 优先处置处罚详细非常
try {
// 主逻辑
}
catch (const NetworkTimeoutException& e) {// 自定义网络超时异常
std::cerr << "[网络] 操作超时: " << e.url() << std::endl;
}
catch (const FileIOException& e) { // 自定义文件异常
std::cerr << "[文件] 错误路径: " << e.path() << std::endl;
}
catch (const std::invalid_argument& e) { // 标准库参数异常
std::cerr << "[参数] " << e.what() << std::endl;
}
catch (const std::exception& e) { // 其他标准异常
std::cerr << "[标准] " << e.what() << std::endl;
}
catch (...) { // 兜底
std::cerr << "无法识别的异常" << std::endl;
}
2.2 记录非常堆栈(必要平台支持)
#include <execinfo.h> // Linux回溯
void print_stacktrace() {
void* buffer;
int size = backtrace(buffer, 64);
char** symbols = backtrace_symbols(buffer, size);
for (int i = 0; i < size; ++i) {
std::cerr << symbols << std::endl;
}
free(symbols);
}
catch (...) {
std::cerr << "异常发生,堆栈追踪:" << std::endl;
print_stacktrace();
return EXIT_FAILURE;
}
3. 自定义非常处置处罚扩展
3.1 统一非常接口
class BaseException : public std::exception {
public:
BaseException(const std::string& msg) : msg_(msg) {}
const char* what() const noexcept override { return msg_.c_str(); }
private:
std::string msg_;
};
class NetworkException : public BaseException {
public:
NetworkException(const std::string& url, int code)
: BaseException("网络错误: " + url + " (代码: " + std::to_string(code) + ")") {}
};
3.2 非常上下文增强
catch (const BaseException& e) {
std::cerr << "[自定义] " << e.what() << std::endl;
// 可在此记录额外信息(如时间戳、会话ID)
}
4. 全局非常处置处罚(可选)
4.1 设置全局非常处置处罚器
#include <csignal>
#include <csetjmp>
std::jmp_buf env;
void signal_handler(int sig) {
std::cerr << "致命信号: " << sig << std::endl;
std::_Exit(EXIT_FAILURE);
}
void terminate_handler() {
std::cerr << "未捕获异常导致程序终止" << std::endl;
std::_Exit(EXIT_FAILURE);
}
int main() {
std::signal(SIGSEGV, signal_handler);
std::set_terminate(terminate_handler);
// ... try-catch 块 ...
}
5. 示例:完整工作流
#include <iostream>
#include <stdexcept>
#include <cstdlib>
void risky_operation(bool fail) {
if (fail) {
throw std::runtime_error("模拟操作失败");
}
std::cout << "操作成功" << std::endl;
}
int main() {
try {
risky_operation(true); // 触发异常
return 0;
} catch (const std::runtime_error& e) {
std::cerr << "运行时错误: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "标准异常: " << e.what() << std::endl;
} catch (...) {
std::cerr << "未知异常" << std::endl;
}
return EXIT_FAILURE;
}
输出:
运行时错误: 模拟操作失败
6. 关键原则
原则实现方式从详细到一般优先捕获详细非常类型,最后catch(...)兜底资源开释包管利用RAII管理资源,确保非常后主动清理错误信息丰富化自定义非常类携带上下文(如错误码、时间戳)跨平台兼容性避免依赖平台特定的非常处置处罚机制(如SEH)日记集成将非常信息写入日记体系,便于事后分析 通过这种分层设计,main()函数能够捕获并报告所有层级的非常,同时确保程序状态可控,资源安全开释。
27 销毁信息前先要找到它的替代者。
在C++中,“销毁信息前先找到替代者” 的战略对于包管数据完整性和体系稳固性至关重要。以下是分步调的详细实现方案及代码示例:
1. 内存管理:先分配新内存,再开释旧内存
动态数组扩容示例
#include <algorithm>
#include <stdexcept>
class DynamicArray {
public:
void resize(size_t new_size) {
// 步骤1:分配新内存
int* new_data = new (std::nothrow) int;
if (!new_data) throw std::bad_alloc();
// 步骤2:复制旧数据到新内存
size_t copy_size = std::min(size_, new_size);
std::copy(data_, data_ + copy_size, new_data);
// 步骤3:安全销毁旧数据(仅在步骤1、2成功后执行)
delete[] data_;
data_ = new_data;
size_ = new_size;
}
private:
int* data_ = nullptr;
size_t size_ = 0;
};
2. 文件操纵:先创建临时文件,再更换原文件
原子性文件更新
#include <fstream>
#include <cstdio>
void safe_file_write(const std::string& path, const std::string& content) {
// 步骤1:写入临时文件
std::string temp_path = path + ".tmp";
{
std::ofstream tmp(temp_path);
if (!tmp) throw std::runtime_error("无法创建临时文件");
tmp << content;
} // 文件流关闭确保数据刷入磁盘
// 步骤2:重命名临时文件替换原文件(原子操作)
if (std::rename(temp_path.c_str(), path.c_str()) != 0) {
std::remove(temp_path.c_str()); // 清理临时文件
throw std::runtime_error("文件替换失败");
}
}
3. 数据结构:先构建新节点,再更新指针
链表节点安全更换
class LinkedList {
struct Node {
int data;
Node* next;
};
public:
void update_node(int old_value, int new_value) {
// 步骤1:创建新节点(可能抛异常)
Node* new_node = new Node{new_value, nullptr};
// 步骤2:定位旧节点并链接新节点
Node* current = head_;
Node* prev = nullptr;
while (current && current->data != old_value) {
prev = current;
current = current->next;
}
if (!current) {
delete new_node; // 未找到旧节点,清理新节点
throw std::invalid_argument("值不存在");
}
// 步骤3:连接新节点到链表
new_node->next = current->next;
if (prev) {
prev->next = new_node;
} else {
head_ = new_node;
}
// 步骤4:安全删除旧节点
delete current;
}
private:
Node* head_ = nullptr;
};
4. 多线程安全:先预备数据,再原子更换
无锁共享指针更换
#include <atomic>
#include <memory>
class ThreadSafeData {
public:
void update_data(const std::string& new_data) {
// 步骤1:创建新数据副本
auto new_ptr = std::make_shared<std::string>(new_data);
// 步骤2:原子替换旧指针
std::atomic_store_explicit(&data_ptr_, new_ptr, std::memory_order_release);
}
std::string get_data() const {
// 原子获取当前数据指针
auto ptr = std::atomic_load_explicit(&data_ptr_, std::memory_order_acquire);
return ptr ? *ptr : "";
}
private:
std::shared_ptr<std::string> data_ptr_;
};
5. 数据库事务:先验证新数据,再提交修改
SQLite事务示例
#include <sqlite3.h>
void safe_db_update(sqlite3* db, int id, const std::string& new_value) {
sqlite3_stmt* stmt = nullptr;
try {
// 步骤1:开始事务
if (sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0) != SQLITE_OK) {
throw std::runtime_error("事务启动失败");
}
// 步骤2:准备更新语句
const char* sql = "UPDATE table SET value=? WHERE id=?";
if (sqlite3_prepare_v2(db, sql, -1, &stmt, 0) != SQLITE_OK) {
throw std::runtime_error("SQL准备失败");
}
// 步骤3:绑定新值并执行
sqlite3_bind_text(stmt, 1, new_value.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, id);
if (sqlite3_step(stmt) != SQLITE_DONE) {
throw std::runtime_error("更新失败");
}
// 步骤4:提交事务
sqlite3_finalize(stmt);
if (sqlite3_exec(db, "COMMIT", 0, 0, 0) != SQLITE_OK) {
throw std::runtime_error("提交失败");
}
} catch (...) {
// 回滚事务并清理资源
sqlite3_exec(db, "ROLLBACK", 0, 0, 0);
sqlite3_finalize(stmt);
throw;
}
}
6. 设计原则总结
原则实现方法关键上风资源获取序次先构造新资源,再销毁旧资源避免资源真空期非常安全利用RAII和try-catch块确保非常发生时资源不走漏原子操纵利用std::atomic或体系级原子操纵包管多线程环境下的可见性和序次性事务处置处罚BEGIN/COMMIT/ROLLBACK模式数据库操纵的一致性数据备份写入临时文件后重定名防止写过程瓦解导致数据损坏 错误处置处罚决议树
通过遵循这些战略,可以确保在销毁旧信息前,其替代者已正确构建并验证,从而提升体系的鲁棒性和数据完整性。
28 在赋值运算中抛出非常前要确保运算对象处于有效状态。
在C++中,实现赋值运算符时,确保在抛出非常前对象处于有效状态是非常安全性的核心要求。以下是分步调的实现战略和代码示例:
1. 非常安全品级与赋值操纵
安全品级形貌赋值操纵实现要点基本包管操纵失败后对象仍处于有效状态不粉碎当前对象的成员数据强包管操纵要么完全成功,要么不影响原对象利用"拷贝并互换"(copy-and-swap)无抛出包管操纵承诺不抛出非常利用noexcept标记,仅限简朴操纵 2. 实现强非常安全的赋值操纵
2.1 拷贝并互换(Copy-and-Swap)惯用法
class String {
public:
// 拷贝构造函数(可能抛出bad_alloc)
String(const String& other)
: data_(new char), size_(other.size_) {
std::copy(other.data_, other.data_ + size_ + 1, data_);
}
// 交换操作(不抛异常)
void swap(String& other) noexcept {
std::swap(data_, other.data_);
std::swap(size_, other.size_);
}
// 赋值运算符(强异常安全)
String& operator=(const String& rhs) {
if (this != &rhs) {
String temp(rhs); // 1. 拷贝构造可能抛异常
swap(temp); // 2. 交换无异常
} // 3. temp析构释放旧资源
return *this;
}
private:
char* data_ = nullptr;
size_t size_ = 0;
};
关键点:
[*]拷贝构造在前:所有可能抛出非常的操纵在修改当前对象前完成。
[*]互换无非常:swap()利用noexcept确保原子性资源转移。
[*]自赋值安全:通过if (this != &rhs)避免冗余操纵。
3. 处置处罚基本非常安全
当无法实现强包管时,至少确保对象有效:
class SafeArray {
public:
SafeArray& operator=(const SafeArray& rhs) {
if (this != &rhs) {
// 1. 分配新内存(可能抛bad_alloc)
int* new_data = new int;
std::copy(rhs.data_, rhs.data_ + rhs.size_, new_data);
// 2. 安全替换(无异常)
delete[] data_;
data_ = new_data;
size_ = rhs.size_;
}
return *this;
}
private:
int* data_ = nullptr;
size_t size_ = 0;
};
风险:
[*]若new int[...]抛出bad_alloc,原data_和size_保持不变(基本包管)。
[*]若std::copy抛出(如元素类型的拷贝抛非常),对象可能处于部分更新状态。
4. 验证非常安全
4.1 单元测试(利用Catch2框架)
TEST_CASE("String赋值运算符强异常安全") {
String s1("Hello");
String s2("World");
// 模拟拷贝构造函数抛出异常
auto throw_on_copy = [](const String&) -> String {
throw std::bad_alloc();
};
REQUIRE_THROWS_AS(s1 = throw_on_copy(s2), std::bad_alloc);
REQUIRE(s1.str() == "Hello"); // 赋值失败后s1仍为原值
}
4.2 内存走漏检测(Valgrind)
valgrind --leak-check=full ./your_program
5. 总结:实现步调与原则
步调实现方式非常安全包管1. 资源拷贝创建临时对象或分配新资源强包管:失败不影响原对象2. 互换/提交利用noexcept的swap()或原子更换无非常3. 清理旧资源在临时对象析构中主动开释旧资源基本包管 核心原则:
[*]先完成可能失败的操纵:在修改当前对象状态前完成所有可能抛出非常的操纵。
[*]资源管理主动化:依赖RAII(如智能指针)避免手动delete。
[*]自赋值查抄:避免冗余操纵和潜在错误。
29 不要让析构函数抛出非常。
在C++中,析构函数绝对不应该抛出非常。这是C++非常安全编程的黄金法则之一,由于析构函数在对象生命周期结束时会被主动调用,若此时抛出非常,会导致程序处于不可猜测的状态甚至直接瓦解。以下是详细缘故原由和解决方案:
一、为什么析构函数不能抛出非常?
1. 栈展开(Stack Unwinding)时的致命问题
当非常被抛出时,C++运行时体系会展开调用栈,逐个调用局部对象的析构函数。假如某个析构函数在栈展开过程中又抛出新的非常:
[*]C++标准规定此时直接调用 std::terminate(),程序立即终止。
[*]资源走漏:未完成的析构函数可能无法开释其他资源。
2. 非常处置处罚机制的冲突
[*]双重非常:若析构函数抛出非常时,当前已有非常在流传(比如在 catch 块中处置处罚其他非常),则触发 std::terminate()。
[*]违反非常安全包管:无法确保对象完整销毁。
3. 设计原则的粉碎
[*]RAII失效:析构函数是资源开释的最后防线,必须包管可靠。
二、正确处置处罚析构函数中的非常
1. 在析构函数中捕获所有非常
class FileHandler {
public:
~FileHandler() noexcept {
try {
if (file_.is_open()) {
file_.close(); // 可能抛出异常
}
} catch (const std::exception& e) {
// 记录日志,但不重新抛出
std::cerr << "文件关闭失败: " << e.what() << std::endl;
}
}
private:
std::fstream file_;
};
关键点:
[*]利用 try-catch 块包裹可能抛出非常的代码。
[*]不重新抛出:在 catch 块中处置处罚错误(如记录日记),但不抛出新非常。
2. 将可能失败的操纵移出析构函数
class DatabaseConnection {
public:
// 显式关闭方法(用户可处理异常)
void close() {
if (conn_.active()) {
conn_.close(); // 可能抛出异常
conn_ = nullptr;
}
}
~DatabaseConnection() noexcept {
try {
close(); // 析构时尝试关闭,但已处理异常
} catch (...) {
std::cerr << "数据库连接未正常关闭" << std::endl;
}
}
private:
DBConnection conn_;
};
上风:
[*]用户可显式调用 close() 并处置处罚非常。
[*]析构函数作为后备,确保资源终极被开释。
三、利用RAII避免非常
1. 智能指针主动管理资源
class ResourceOwner {
public:
ResourceOwner() : res_(std::make_unique<Resource>()) {}
// 无需自定义析构函数,unique_ptr自动释放资源
private:
std::unique_ptr<Resource> res_;
};
2. 确保资源开释操纵无非常
[*]设计不可失败的接口:class MutexGuard {
public:
~MutexGuard() noexcept {
mutex_.unlock(); // 假设unlock()永远不会失败
}
private:
std::mutex& mutex_;
};
四、代码验证与工具
1. 静态查抄工具
[*]Clang-Tidy查抄:clang-tidy -checks='-*,bugprone-exception-escape' your_file.cpp
[*]检测可能从析构函数抛出的非常。
2. 单元测试
TEST_CASE("析构函数不抛异常") {
REQUIRE_NOTHROW([] {
FileHandler fh;
}());
}
五、总结:析构函数非常处置处罚原则
原则实现方式示例克制抛出非常析构函数标记为noexcept~ClassName() noexcept { ... }内部非常完全捕获用try-catch包裹可能失败的操纵文件关闭、网络毗连开释资源开释操纵无非常设计资源管理类,确保开释操纵可靠智能指针、RAII包装类提供显式开释方法答应用户处置处罚非常,析构函数作为后备close(), disconnect() 六、错误处置处罚决议树
通过遵循这些原则,可确保析构函数不会抛出非常,从而维护程序的稳固性和可猜测性。
30 把普通代码和非常处置处罚代码分离开来。
在C++中,将普通代码(业务逻辑)与非常处置处罚代码分离是提升代码可维护性和可读性的重要本领。以下是分步调的实现战略和代码示例:
一、核心设计原则
原则实现方式上风资源与逻辑分离利用RAII管理资源,避免非常处置处罚代码侵入业务逻辑业务代码专注流程,非常处置处罚主动完成分层非常处置处罚在独立层(如控制器、中间件)统一处置处罚非常避免重复try-catch块非常安全封装将可能抛出非常的代码封装到特定模块业务代码仅调用接口,不处置处罚细节错误传递战略利用非常或错误码规范错误流传路径统一错误信息格式,方便追踪 二、详细实现战略
1. RAII资源管理(主动清理,减少try-catch)
// 业务代码:无需显式异常处理
void process_data(const std::string& path) {
FileRAII file(path);// RAII自动管理文件句柄
DataProcessor processor(file);
processor.analyze();
// 无需手动关闭文件,析构时自动处理
}
// RAII包装类
class FileRAII {
public:
explicit FileRAII(const std::string& path)
: file_(fopen(path.c_str(), "r")) {
if (!file_) throw FileOpenError(path);
}
~FileRAII() noexcept { if (file_) fclose(file_); }
FILE* handle() const noexcept { return file_; }
private:
FILE* file_;
};
2. 业务逻辑与非常处置处罚分层
// 业务层:纯逻辑,不处理异常
void business_operation() {
DatabaseConnection db("user:pass@host");
db.execute("UPDATE accounts SET balance = balance * 1.05");
}
// 控制层:统一异常处理
int main() {
try {
business_operation();
return 0;
} catch (const DatabaseException& e) {
log_error("数据库错误:", e.what());
return 1;
} catch (const std::exception& e) {
log_error("系统错误:", e.what());
return 2;
} catch (...) {
log_error("未知错误");
return 3;
}
}
3. 非常生成与处置处罚模块化
// 异常生成模块:封装可能失败的操作
namespace risky_ops {
Image load_image(const std::string& path) {
if (!file_exists(path))
throw ImageLoadError("文件不存在: " + path);
return decode_image(path); // 可能抛异常
}
}
// 业务代码:调用模块化接口
void display_image(const std::string& path) {
try {
auto img = risky_ops::load_image(path);
render(img);
} catch (...) {
// 仅在此处理UI相关错误(如显示错误弹窗)
show_error_dialog("图片加载失败");
throw; // 其他异常继续向上传递
}
}
4. 错误码与非常转换(混淆战略)
// 底层:返回错误码
ErrorCode low_level_operation(int param) {
if (param < 0) return ErrorCode::InvalidInput;
// ...操作...
return ErrorCode::Success;
}
// 中间层:将错误码转换为异常
void mid_layer(int param) {
auto code = low_level_operation(param);
if (code != ErrorCode::Success) {
throw AppException("操作失败", static_cast<int>(code));
}
}
// 业务层:仅处理异常
void business_logic() {
try {
mid_layer(42);
} catch (const AppException& e) {
// 处理业务异常
}
}
三、高级本领
1. 战略模式实现可插拔非常处置处罚
class ErrorHandler {
public:
virtual ~ErrorHandler() = default;
virtual void handle(const std::exception& e) const = 0;
};
class ConsoleHandler : public ErrorHandler {
public:
void handle(const std::exception& e) const override {
std::cerr << "错误: " << e.what() << std::endl;
}
};
class DatabaseWriter {
std::unique_ptr<ErrorHandler> handler_;
public:
explicit DatabaseWriter(std::unique_ptr<ErrorHandler> handler)
: handler_(std::move(handler)) {}
void save(const Data& data) {
try {
db_.insert(data);
} catch (const std::exception& e) {
handler_->handle(e); // 委托给策略处理
}
}
};
2. 利用std::optional或std::expected(C++23)减少非常
// 业务代码使用optional处理可能失败的操作
std::optional<Image> safe_load(const std::string& path) {
if (!validate_path(path)) return std::nullopt;
return decode_image(path); // 内部可能抛异常,但被封装
}
void display_image() {
if (auto img = safe_load("photo.jpg")) {
render(*img);
} else {
show_error("无效图片路径");
}
}
四、验证与调试
1. 单元测试分离
// 测试业务逻辑(假设无异常)
TEST_CASE("数据分析逻辑正确性") {
DataSet data = generate_test_data();
REQUIRE(process(data) == expected_result);
}
// 单独测试异常处理
TEST_CASE("文件不存在时抛出异常") {
REQUIRE_THROWS_AS(load_image("invalid.jpg"), ImageLoadError);
}
2. 静态分析工具
# 使用Clang-Tidy检查异常安全
clang-tidy -checks='*' your_file.cpp -- -std=c++20
五、总结:代码结构示例
src/
├── core/ # 核心业务逻辑(无异常处理)
│ ├── data_processor.cpp
│ └── database.cpp
├── exceptions/ # 异常处理模块
│ ├── handlers/ # 异常处理策略
│ └── errors.cpp # 自定义异常类
└── main.cpp # 统一异常捕获入口
通过以上战略,可实现:
[*]业务代码纯净:专注于核心逻辑,无混杂的非常处置处罚代码
[*]非常处置处罚集中:统一管理错误日记、用户提示、资源回滚
[*]可维护性提升:修改非常战略时无需改动业务逻辑
[*]测试友好:业务逻辑和非常处置处罚可分别进行单元测试
31 当非常发生时,假如由new分配的内存尚未被开释将造成内存走漏,请注意这一点。
在C++中,当利用 new 分配内存后,若未正确开释且发生非常,会导致内存走漏。为确保非常安全,需遵循 RAII(Resource Acquisition Is Initialization) 原则,结合智能指针和公道的代码设计。以下是详细解决方案和代码示例:
一、问题根源与风险
错误示例:直接利用 new 导致走漏
void risky_function() {
int* ptr = new int;// 分配内存
some_operation(); // 可能抛异常
delete[] ptr; // 若异常发生,此行不会执行!
}
风险:若 some_operation() 抛出非常,delete[] 无法实验,内存走漏。
二、解决方案
1. 利用智能指针(推荐)
智能指针(如 std::unique_ptr 和 std::shared_ptr)会在析构时主动开释内存,纵然发生非常。
示例:std::unique_ptr
#include <memory>
void safe_function() {
auto ptr = std::make_unique<int[]>(100);// 自动管理内存
some_operation();// 若抛异常,ptr 析构时自动释放内存
}
示例:std::shared_ptr
void shared_resource() {
auto ptr = std::shared_ptr<int>(new int(42), [](int* p) { delete p; });
some_operation();// 异常安全
}
2. 手动 try-catch 开释(不推荐)
若必须手动管理,需在 try 块中开释内存。
void manual_management() {
int* ptr = nullptr;
try {
ptr = new int;
some_operation();
delete[] ptr;
} catch (...) {
delete[] ptr;// 捕获异常后释放
throw; // 重新抛出异常
}
}
缺点:代码冗余,易遗漏开释逻辑。
三、复杂场景:构造函数中的非常
问题:构造函数中分配多个资源
class ResourceHolder {
public:
ResourceHolder() {
ptr1 = new int;// 分配资源1
ptr2 = new int;// 分配资源2(可能抛异常)
}
~ResourceHolder() { delete[] ptr1; delete[] ptr2; }
private:
int* ptr1;
int* ptr2;
};
风险:若 ptr2 分配失败,ptr1 未被开释。
解决方案:用智能指针管理成员
class SafeResourceHolder {
public:
SafeResourceHolder()
: ptr1(std::make_unique<int[]>(100)),
ptr2(std::make_unique<int[]>(200)) {}
// 无需手动编写析构函数
private:
std::unique_ptr<int[]> ptr1;
std::unique_ptr<int[]> ptr2;
};
上风:纵然构造函数中途失败,已分配的资源也会被主动开释。
四、进阶:自定义RAII类
封装文件句柄管理
#include <cstdio>
class FileRAII {
public:
explicit FileRAII(const char* path) : file_(fopen(path, "r")) {
if (!file_) throw std::runtime_error("文件打开失败");
}
~FileRAII() noexcept { if (file_) fclose(file_); }
FILE* handle() const noexcept { return file_; }
private:
FILE* file_;
};
void process_file() {
FileRAII file("data.txt");// 异常安全
// 使用 file.handle()
}
五、非常安全品级
品级形貌实现方式基本包管非常发生后程序状态有效,但可能部分修改确保所有资源被开释强包管非常发生后程序状态与调用前一致利用"拷贝并互换"(copy-and-swap)无抛出包管操纵绝不抛非常利用 noexcept 标记 六、错误处置处罚决议树
总结
战略适用场景上风智能指针常规动态内存管理主动开释,代码简便RAII封装类文件句柄、网络毗连等非内存资源统一资源生命周期管理手动try-catch遗留代码或特别需求机动但易堕落,需严格测试 通过将资源管理与对象生命周期绑定,可从根本上避免因非常导致的内存走漏。优先利用智能指针和RAII机制,而非手动管理。
32 函数假如能抛出一个非常,那么它就会抛出这个非常,遵循这一假设。
在C++中,遵循“若函数可能抛出非常,则必须确保非常正确抛出”的假设,必要通过以下战略实现健壮的非常安全性:
一、确保非常正确抛出的关键原则
[*] 非常安全包管品级:
[*]基本包管:非常发生时,对象仍处于有效状态,资源不走漏。
[*]强包管:操纵要么完全成功,要么不改变程序状态(原子性)。
[*]无抛出包管(noexcept):操纵绝不抛出非常。
[*] 资源管理:
[*]利用RAII(如智能指针、自定义资源句柄)确保资源主动开释。
[*]析构函数标记为noexcept,且绝不抛出非常。
二、实现非常正确抛出的代码规范
1. 显式抛出非常的条件查抄
class DataProcessor {
public:
void process(const std::string& input) {
if (input.empty()) {
throw std::invalid_argument("输入不可为空");
}
// 业务逻辑...
}
};
2. 利用RAII包管资源安全
void safe_file_operation(const std::string& path) {
std::ofstream file(path); // RAII自动管理文件流
if (!file) {
throw std::runtime_error("无法打开文件: " + path);
}
// 文件操作...
} // 文件流析构时自动关闭
3. 强非常包管:Copy-and-Swap
class Config {
public:
void update(const std::string& key, const std::string& value) {
auto temp = data_; // 拷贝原数据
temp = value; // 修改副本(可能抛异常)
data_.swap(temp); // 无异常则原子交换
}
private:
std::map<std::string, std::string> data_;
};
4. 非常流传与封装
void high_level_operation() {
try {
low_level_operation(); // 可能抛异常
} catch (const LowLevelException& e) {
// 添加上下文后重新抛出
throw HighLevelException("高层操作失败", e);
}
}
三、非常处置处罚的代码分层
1. 业务层(纯逻辑,无try-catch)
void business_logic() {
DatabaseConnection db("user:pass@host");
db.execute("UPDATE accounts SET balance = balance * 1.05");
}
2. 控制层(统一非常处置处罚)
int main() {
try {
business_logic();
return 0;
} catch (const DatabaseException& e) {
std::cerr << "数据库错误: " << e.what() << std::endl;
return 1;
} catch (const std::exception& e) {
std::cerr << "系统错误: " << e.what() << std::endl;
return 2;
} catch (...) {
std::cerr << "未知异常" << std::endl;
return 3;
}
}
四、自定义非常类设计
1. 继承自std::exception
class NetworkException : public std::runtime_error {
public:
NetworkException(const std::string& url, int code)
: std::runtime_error("网络错误: " + url + " (代码: " + std::to_string(code) + ")"),
url_(url), code_(code) {}
const std::string& url() const noexcept { return url_; }
int code() const noexcept { return code_; }
private:
std::string url_;
int code_;
};
2. 利用非常链传递上下文
try {
connect_to_server("https://example.com");
} catch (const SocketException& e) {
throw NetworkException("example.com", 500).with_context(e);
}
五、工具与验证
1. 静态分析工具
# 使用Clang-Tidy检查异常安全性
clang-tidy -checks='*' your_file.cpp -- -std=c++20
2. 单元测试验证
TEST_CASE("空输入触发异常") {
DataProcessor processor;
REQUIRE_THROWS_AS(processor.process(""), std::invalid_argument);
}
TEST_CASE("文件打开失败时抛出异常") {
REQUIRE_THROWS_AS(safe_file_operation("/invalid/path"), std::runtime_error);
}
六、性能与非常处置处罚的平衡
场景战略工具/技术性能敏感路径禁用非常,利用错误码或std::expected-fno-exceptions编译选项可规复错误抛出自定义非常类非常层次结构设计不可规复错误终止程序(std::terminate)断言或日记记录后终止 七、总结:非常处置处罚决议树
通过严格遵循这些原则和模式,可确保非常在预期场景下正确抛出,同时保持代码的健壮性和可维护性。
33 库不应自行终止程序,正确的做法是抛出一个非常然后由调用者决定该怎么做。
在C++库设计中,遵循“库不应自行终止程序,而应抛出非常”的原则,可确保调用者拥有完全的错误处置处罚控制权。以下是详细实现步调与示例:
一、设计原则
[*]绝不调用终止函数:避免利用 std::terminate、abort 或 exit。
[*]资源安全:通过RAII管理资源,确保非常抛出时无走漏。
[*]非常类型明确:自定义非常类,传递详细错误信息。
[*]文档清晰:明确标注可能抛出的非常类型及触发条件。
二、实现步调
1. 定义自定义非常类
#include <stdexcept>
#include <string>
class LibraryException : public std::runtime_error {
public:
explicit LibraryException(const std::string& msg, int error_code = 0)
: std::runtime_error(msg), error_code_(error_code) {}
int code() const noexcept { return error_code_; }
private:
int error_code_;
};
class FileOpenException : public LibraryException {
public:
explicit FileOpenException(const std::string& path, int errno_code)
: LibraryException("无法打开文件: " + path, errno_code), path_(path) {}
const std::string& path() const { return path_; }
private:
std::string path_;
};
2. 资源管理:利用RAII
class SafeFile {
public:
explicit SafeFile(const std::string& path) : file_(fopen(path.c_str(), "r")) {
if (!file_) throw FileOpenException(path, errno);
}
~SafeFile() noexcept { if (file_) fclose(file_); }
FILE* handle() const noexcept { return file_; }
private:
FILE* file_;
};
3. 函数实现:抛出非常而非终止
// 正确做法:抛出异常
void process_data(const std::string& path) {
SafeFile file(path); // 可能抛出FileOpenException
// 处理文件...
}
// 错误做法:终止程序
void bad_process_data(const std::string& path) {
FILE* file = fopen(path.c_str(), "r");
if (!file) {
std::cerr << "致命错误:无法打开文件" << std::endl;
std::exit(EXIT_FAILURE); // ❌ 库不应自行终止
}
// 处理文件...
fclose(file);
}
4. 错误流传:不吞噬非常
void low_level_operation() {
if (critical_error_detected()) {
throw LibraryException("底层操作失败", error_code);
}
}
void high_level_api() {
try {
low_level_operation();
} catch (const LibraryException& e) {
// 可添加上下文后重新抛出
throw LibraryException("高层操作失败: " + std::string(e.what()), e.code());
}
}
5. 提供错误码选项(可选)
enum class ErrorCode { Success = 0, InvalidInput, ResourceBusy };
ErrorCode safe_operation(int param) noexcept {
try {
if (param < 0) throw LibraryException("参数无效", static_cast<int>(ErrorCode::InvalidInput));
// 操作...
return ErrorCode::Success;
} catch (const LibraryException& e) {
return static_cast<ErrorCode>(e.code());
} catch (...) {
return ErrorCode::ResourceBusy;
}
}
三、调用者处置处罚示例
int main() {
try {
process_data("data.txt");
} catch (const FileOpenException& e) {
std::cerr << "文件错误: " << e.what()
<< "\n路径: " << e.path()
<< "\n系统错误码: " << e.code() << std::endl;
// 恢复策略:使用默认数据
load_default_data();
} catch (const LibraryException& e) {
std::cerr << "库错误[" << e.code() << "]: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
四、文档标注
在头文件中明确标注可能抛出的非常:
/**
* @brief 处理指定文件的数据
* @param path 文件路径
* @throws FileOpenException 文件无法打开时抛出
* @throws LibraryException 其他库内部错误时抛出
*/
void process_data(const std::string& path);
五、测试验证
编写单元测试验证非常举动:
#include <catch2/catch.hpp>
TEST_CASE("文件打开失败时抛出FileOpenException") {
REQUIRE_THROWS_AS(process_data("nonexistent.txt"), FileOpenException);
}
TEST_CASE("无效参数触发LibraryException") {
REQUIRE_THROWS_AS(safe_operation(-1), LibraryException);
}
六、总结
原则实现方式上风非常而非终止用throw更换exit/abort调用者可机动处置处罚错误资源主动开释RAII类管理文件句柄、内存、锁等非常安全无走漏非常类型分层自定义非常继承自标准非常错误信息丰富且类型安全文档明确性头文件中标注@throws用户清晰了解可能的错误条件 通过此设计,库将错误控制权完全交给调用者,提升代码的健壮性和可维护性。
34 库不应该直接输出面向终极用户的错误诊断信息,正确的做法是抛出一个非常然后由调用者决定该怎么做。
在C++库设计中,避免直接输出用户可见的错误信息,转而通过非常机制将错误传递给调用者处置处罚,是提高机动性和可维护性的关键。以下是分步调的实现方案:
一、定义非常类体系
1. 底子非常类(继承自 std::exception)
#include <stdexcept>
#include <string>
class LibraryException : public std::runtime_error {
public:
explicit LibraryException(const std::string& msg, int error_code = 0)
: std::runtime_error(msg), error_code_(error_code) {}
int code() const noexcept { return error_code_; }
virtual std::string details() const { return ""; }
private:
int error_code_;
};
2. 详细非常类(按错误类型细化)
// 文件操作异常
class FileIOException : public LibraryException {
public:
FileIOException(const std::string& path, int errno_code)
: LibraryException("文件I/O错误: " + path, errno_code), path_(path) {}
std::string details() const override {
return "路径: " + path_ + ",系统错误码: " + std::to_string(code());
}
private:
std::string path_;
};
// 网络异常
class NetworkException : public LibraryException {
public:
NetworkException(const std::string& url, int http_status)
: LibraryException("网络请求失败: " + url, http_status), url_(url) {}
std::string details() const override {
return "URL: " + url_ + ",HTTP状态码: " + std::to_string(code());
}
private:
std::string url_;
};
二、实现非常安全的库函数
1. 抛出非常而非输堕落误
#include <fstream>
#include <vector>
std::vector<char> read_file(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) {
throw FileIOException(path, errno); // 抛出而非输出到stderr
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (!file.read(buffer.data(), size)) {
throw FileIOException(path, errno);
}
return buffer;
}
2. 利用RAII管理资源
class DatabaseConnection {
public:
explicit DatabaseConnection(const std::string& conn_str)
: handle_(connect(conn_str))
{
if (!handle_.active()) {
throw NetworkException(conn_str, -1);
}
}
void execute(const std::string& sql) {
// 执行SQL,失败时抛异常
}
private:
DBHandle handle_; // RAII管理连接
};
三、调用者处置处罚非常
1. 捕获并处置处罚非常
int main() {
try {
auto data = read_file("config.json");
DatabaseConnection db("user:pass@localhost");
db.execute("UPDATE settings SET value=1");
} catch (const FileIOException& e) {
// 记录到日志系统,而非直接输出
log_error("文件错误[代码%d]: %s\n详情: %s",
e.code(), e.what(), e.details().c_str());
return EXIT_FAILURE;
} catch (const NetworkException& e) {
show_user_alert("网络连接失败: " + e.details()); // 用户界面提示
return EXIT_FAILURE;
} catch (const LibraryException& e) {
log_error("库内部错误: %s (代码%d)", e.what(), e.code());
return EXIT_FAILURE;
} catch (...) {
log_error("未知异常");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
四、文档与接口规范
1. 头文件注释
/**
* @brief 读取文件内容到字节数组
* @param path 文件路径
* @return 包含文件内容的std::vector<char>
* @throws FileIOException 文件打开或读取失败时抛出
* @throws std::bad_alloc 内存不足时抛出
*/
std::vector<char> read_file(const std::string& path);
2. 非常类型文档
非常类触发场景携带信息FileIOException文件无法打开或读取文件路径、体系错误码NetworkException网络请求失败URL、HTTP状态码LibraryException库内部未分类错误错误消息、自定义错误码 五、单元测试验证
#include <catch2/catch.hpp>
TEST_CASE("读取不存在的文件应抛出FileIOException") {
REQUIRE_THROWS_AS(read_file("nonexistent.txt"), FileIOException);
}
TEST_CASE("网络连接失败时抛出NetworkException") {
REQUIRE_THROWS_AS(DatabaseConnection("invalid_conn"), NetworkException);
}
六、设计原则总结
原则实现方式上风非常而非输出抛出携带详细信息的非常类调用者机动处置处罚错误资源主动开释RAII类管理文件句柄、网络毗连等非常安全无走漏非常分类清晰继承体系区分差别错误类型精准捕获和处置处罚特定错误文档完整性头文件和独立文档形貌非常举动调用者明确了解错误条件 通过此设计,库将错误处置处罚权完全交给调用者,同时确保错误信息的丰富性和可追溯性,符合现代C++库的最佳实践。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]