C++程序设计语言笔记——基本功能:异常处理

发布于:2025-03-12 ⋅ 阅读:(15) ⋅ 点赞:(0)

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 << "[Business Error] Code: " << e.get_code() 
                  << ", Msg: " << e.what() << "\n";
    } catch (const std::exception& e) {
        std::cerr << "[Fatal] " << 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[size]) {}
    
    // 移动构造函数标记为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 {…}
强异常保证 先修改副本,再swap std::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 ...
关键资源获取失败(文件、网络) 立即抛出异常 文件打开失败、数据库连接断开
外部服务返回不可恢复错误 抛出携带上下文的业务异常 支付网关返回余额不足
数据校验失败且无法继续流程 抛出带错误码的验证异常 用户提交的表单数据非法
需要跨多层调用处理错误 抛出异常而非逐层返回错误码 深层嵌套的业务逻辑错误

关键设计总结

  1. 明确语义:每个异常类型对应具体的错误场景,避免通用std::exception直接抛出
  2. 资源安全:结合RAII和智能指针确保异常发生时资源自动释放
  3. 性能权衡:在实时系统中评估异常开销,必要时使用错误码
  4. 文档契约:在函数注释中明确说明可能抛出的异常类型
  5. 统一处理:在应用入口集中捕获未处理异常,记录日志后安全退出

通过遵循这些准则,可在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);
}

总结

  1. 明确异常用途:仅对不可恢复或关键错误使用异常。
  2. 资源管理:结合RAII确保资源安全释放。
  3. 异常安全:设计操作保证基本或强异常安全。
  4. 分层处理:在适当层级捕获异常(如UI层捕获并显示错误)。
  5. 性能考量:避免在频繁执行路径中使用异常。
  6. 文档化:在函数注释中注明可能抛出的异常类型。

通过合理使用异常机制,可以显著提升代码的可读性和健壮性,将错误处理逻辑与业务逻辑分离,便于维护和调试。

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. 设计原则总结

原则 实现方法
明确语义 每个异常类对应具体错误场景(如FileIOExceptionNetworkException
丰富上下文 在异常类中添加任务相关数据(路径、错误码、时间戳等)
类型安全 通过继承层次区分错误类型,避免使用通用异常
资源安全 使用智能指针管理动态分配的原因异常(std::unique_ptr<BaseException>
可扩展性 通过基类提供统一接口(what()code()),派生类扩展数据
兼容性 可选:若需与标准异常交互,可多重继承std::exception(但根据用户需求省略)

性能优化建议

  1. 避免频繁抛出:在性能关键路径(如循环内部)优先使用错误码。
  2. 移动语义:确保异常类支持移动构造,减少拷贝开销。
    class BaseException {
    public:
        BaseException(BaseException&&) = default;
        BaseException& operator=(BaseException&&) = default;
    };
    
  3. 轻量级设计:避免在异常类中存储过大数据(如完整响应内容可存指针)。

单元测试示例

#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;
}

关键设计原则

  1. 统一错误接口:所有函数使用相同的错误返回方式(如Result<T>
  2. 错误信息丰富化:携带足够上下文用于调试
  3. 资源安全:通过守卫对象模拟RAII
  4. 错误处理集中化:在高层级统一处理错误,避免分散的错误检查
  5. 错误链支持:追踪错误根源,类似异常的堆栈回溯

通过这种方式,可以在禁用异常的环境中构建可维护的错误处理系统,同时保持代码的结构化。对于性能关键代码,可进一步优化错误对象的内存分配(如使用预分配错误池)。

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() << " [Code: " << e.code() << "]\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: 更新数据失败 [Code: 2001]
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. 分层处理决策树

抛出
捕获并转换
传递到
数据访问操作
DaoException
业务层能否处理?
业务层处理并恢复
包装为ServiceException
UI层能否处理?
显示友好提示
全局处理器记录日志并终止

5. 性能优化建议

  1. 避免过度包装:仅在跨层边界时包装异常
  2. 使用noexcept标记:对不会失败的函数(如简单getter)使用noexcept
    std::string getErrorMessage() const noexcept {
        return msg_;
    }
    
  3. 错误码替代:对高频调用的底层函数使用错误码返回
    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);
}

总结

通过层次化异常处理设计,可实现:

  1. 关注点分离:每层专注处理其职责范围内的错误
  2. 信息丰富化:异常链携带完整错误上下文
  3. 用户友好:UI层隔离技术细节,展示清晰提示
  4. 维护性提升:异常类型与业务语义直接关联

关键原则:底层捕获技术异常,高层处理业务语义,顶层保证用户体验。建议结合静态分析工具(如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[key] = 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

最佳实践总结

  1. 抛出清晰:每个异常应携带足够诊断信息(错误码、上下文)
  2. 资源无忧:通过RAII和智能指针确保资源自动释放
  3. 层级过滤:技术异常在底层抛出,业务层转换语义,UI层友好提示
  4. 安全保证:明确函数提供的异常安全等级(基本/强/noexcept)
  5. 监控到位:全局处理器记录未捕获异常,结合日志分析

通过遵循这些原则,可在保持代码简洁的同时构建健壮的异常处理体系,避免陷入"异常处理反模式"的陷阱。记住:好的异常处理是战略性的,而非事无巨细的战术防御

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_datadelete old
无副作用修改 先操作副本,成功后再修改主数据 temp.swap(data_)
原子性操作 使用swapmove实现无异常操作 vector::swap
避免裸资源管理 优先使用标准库容器而非new/delete std::vector代替int[]

通过遵循这些实践,即使代码中发生异常,也能确保:

  1. 资源不泄漏:所有已分配的内存、文件句柄、网络连接等都会被正确释放
  2. 对象有效:所有对象处于可安全析构的状态
  3. 数据一致性:关键数据结构不会处于半完成状态

最终建议:在类的设计文档中明确标注每个方法的异常安全等级(基本/强/无异常),作为与团队约定的契约。

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[key] = 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[42] = 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的移动构造
延迟修改 需要原子性的批量操作 先收集所有修改,最后一次性提交

何时可以放宽要求?

场景 可接受的安全等级 理由
性能关键路径 基本保证 强安全开销超过性能预算
临时对象操作 无保证 对象即将销毁,状态无关紧要
错误统计 基本保证 计数器误差可接受
硬件交互 无保证 物理设备状态无法回滚

最佳实践总结

  1. 默认选择强安全:除非有明确理由否则优先实现
  2. 利用标准库设施vector::push_backmap::emplace
  3. 事务封装:对复杂操作使用RAII事务包装器
  4. 移动优于拷贝:用移动语义减少临时对象创建
  5. 明确文档契约:在函数注释中声明安全等级

通过遵循这些原则,可以在大多数场景下以合理成本实现强异常安全,构建出既健壮又高效的C++系统。

10 让构造函数建立不变式,如果不能,则抛出异常。

在C++中,构造函数是建立类不变式(class invariants)的理想位置。如果构造函数无法满足类的不变式要求,应当抛出异常以确保对象不会被部分构造或处于无效状态。以下是具体实现策略和代码示例:


1. 基本设计原则

  1. 不变式定义:明确类在任何时刻必须满足的条件(如0 < month <= 12)。
  2. 构造函数验证:在构造函数中强制检查不变式条件。
  3. 异常抛出:若不变式无法建立,立即抛出异常阻止对象创建。
  4. 资源安全:使用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[4];
        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_argument throw std::invalid_argument(...)
资源分配失败 抛出std::runtime_error或其派生类 throw std::runtime_error(...)
依赖项初始化失败 允许异常传播,RAII自动清理 不捕获,依赖析构函数释放资源
多阶段验证 在成员初始化列表中尽早失败 : member(validate(arg))

关键原则总结

  1. 失败前置:在构造函数体执行前验证参数(使用成员初始化列表)
  2. 原子性构造:要么完全成功,要么抛出异常(无半构造对象)
  3. 资源自治:每个成员自身管理资源(使用智能指针、容器等)
  4. 异常类型明确:根据错误类型选择标准或自定义异常
  5. 禁止吞异常:构造函数不应捕获无关异常

通过遵循这些原则,可以确保类的实例始终处于有效状态,同时保持代码的健壮性和可维护性。

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[1024];
    fread(buffer, 1, sizeof(buffer), file.handle());
}

4. 使用标准库容器管理资源

示例:自动释放动态数组
#include <vector>

void safe_array_operation() {
    std::vector<int> array(1000); // 自动管理内存
    array[0] = 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优于手动释放?

  1. 异常安全:即使抛出异常,析构函数仍会调用。
  2. 代码简洁:消除重复的try-catchdelete/free调用。
  3. 维护性强:资源生命周期清晰可见。
  4. 性能优化:减少冗余错误检查代码。

验证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-elsewhile)而非异常。以下是具体场景和最佳实践:


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[1024];
    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技术:

  1. 消除手动资源管理:避免new/deleteopen/close的配对问题
  2. 保证异常安全:即使发生异常,资源也能正确释放
  3. 提升代码可读性:资源生命周期清晰可见
  4. 支持更复杂的资源策略:通过组合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[1024];
    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_(size) {}
    
    ~DynamicArrayRAII() noexcept { delete[] data_; }

    T& operator[](size_t index) { return data_[index]; }

private:
    T* data_;
    size_t size_;
};

// 使用示例
void process_data() {
    DynamicArrayRAII<int> arr(1000); // 分配内存
    arr[0] = 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技术,可以:

  1. 消除手动资源管理:避免new/deleteopen/close的配对错误。
  2. 保证异常安全:即使发生异常,资源也能正确释放。
  3. 提升代码可读性:资源生命周期与对象作用域一致。
  4. 支持复杂资源策略:通过组合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[0]);
        return EXIT_FAILURE;  // 直接退出无需恢复状态
    }
    process_file(argv[1]);    // 单次执行无需回滚
}

特点

  • 错误直接导致程序终止
  • 无长期运行状态需要维护
  • 资源管理依赖操作系统自动回收

二、非异常安全代码编写规范

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;

五、决策流程图

需要跨多层处理错误?
使用异常
代码运行在实时系统?
禁用异常,用错误码
性能要求>200ns/op?
可选RAII+错误码

总结

当开发一次性工具、实时系统、性能关键型组件时,可放弃异常安全机制,通过:

  1. 严格配对手工资源管理
  2. 清晰错误码传递体系
  3. 断言辅助调试
  4. 作用域守卫工具
    来构建可靠系统。但需在项目文档中明确标注非异常安全模块,限制其使用范围。

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[4];
        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[key] = 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和异常处理,可以确保:

  1. 资源安全:无论是否发生异常,资源都会被正确释放。
  2. 状态一致性:对象始终保持有效状态。
  3. 代码简洁性:消除冗余的错误检查代码。

关键准则:每个类的设计都应明确其不变式,并通过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资源句柄通过

  1. 作用域绑定
  2. 自动析构顺序
  3. 异常安全保证
  4. 零开销抽象
    成为资源管理的首选方案,远优于需要手动维护的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[MatrixSize][MatrixSize];
    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或异常)
触发时机 编译阶段 程序运行时
性能影响 无额外开销 可能引入分支判断和异常处理开销
错误定位 直接定位源码位置 需调试或日志分析
适用场景 类型特征、常量表达式、模板参数等确定性问题 用户输入、文件状态等动态不确定性问题

七、最佳实践总结

  1. 优先编译时检查:对类型、常量表达式等确定性问题使用static_assert
  2. 结合类型特征库:利用<type_traits>中的工具简化约束条件
  3. 明确错误信息:提供清晰的静态断言消息,方便开发者定位问题
  4. 避免滥用:动态条件(如用户输入)仍需运行时检查

通过将尽可能多的检查移至编译时,可以显著提升代码质量和运行效率,减少调试成本。

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层 防止崩溃,统一错误格式化
日志系统 所有层 错误追踪和审计

七、分层策略优势

  1. 关注点分离:每层仅处理职责内的错误类型
  2. 错误信息丰富化:异常在传递过程中携带更多上下文
  3. 资源安全:RAII确保任何层级抛出异常都不会泄漏资源
  4. 用户体验优化:顶层将技术细节转换为友好提示
  5. 维护性提升:编译时检查减少运行时错误可能性

通过这种分层策略,可以构建出既健壮又易于维护的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. 基于契约的编程
  • 工具:使用assertstatic_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. 使用静态分析工具

四、总结:最佳实践

场景 处理方式 工具/技术
函数绝不抛异常 使用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::exception class FileException : public BaseException
抛出异常 在适当位置抛出具体的派生类异常 throw FileException(path, errno)
引用捕获 使用const&捕获异常 catch (const FileException& e)
捕获顺序 从具体到一般 FileException,后BaseException
避免对象切片 绝不通过值捕获多态异常 不用catch (BaseException e)
异常安全 结合RAII管理资源 std::lock_guard, 智能指针

7. 错误处理决策树

捕获异常
是否已知具体类型?
直接处理具体异常
是否为自定义基类异常?
使用dynamic_cast检查具体类型
是否为std::exception?
处理标准异常
处理未知异常

通过引用捕获层次化的异常,结合清晰的异常类型设计,可以构建出既灵活又安全的错误处理系统。

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[64];
    int size = backtrace(buffer, 64);
    char** symbols = backtrace_symbols(buffer, size);
    for (int i = 0; i < size; ++i) {
        std::cerr << symbols[i] << 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[new_size];
        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[other.size_ + 1]), 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[rhs.size_];
            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. 交换/提交 使用noexceptswap()或原子替换 无异常
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::optionalstd::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         # 统一异常捕获入口

通过以上策略,可实现:

  1. 业务代码纯净:专注于核心逻辑,无混杂的异常处理代码
  2. 异常处理集中:统一管理错误日志、用户提示、资源回滚
  3. 可维护性提升:修改异常策略时无需改动业务逻辑
  4. 测试友好:业务逻辑和异常处理可分别进行单元测试

31 当异常发生时,如果由new分配的内存尚未被释放将造成内存泄漏,请注意这一点。

在C++中,当使用 new 分配内存后,若未正确释放且发生异常,会导致内存泄漏。为确保异常安全,需遵循 RAII(Resource Acquisition Is Initialization) 原则,结合智能指针和合理的代码设计。以下是详细解决方案和代码示例:


一、问题根源与风险

错误示例:直接使用 new 导致泄漏
void risky_function() {
    int* ptr = new int[100];  // 分配内存
    some_operation();         // 可能抛异常
    delete[] ptr;             // 若异常发生,此行不会执行!
}

风险:若 some_operation() 抛出异常,delete[] 无法执行,内存泄漏。


二、解决方案

1. 使用智能指针(推荐)

智能指针(如 std::unique_ptrstd::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[100];
        some_operation();
        delete[] ptr;
    } catch (...) {
        delete[] ptr;  // 捕获异常后释放
        throw;         // 重新抛出异常
    }
}

缺点:代码冗余,易遗漏释放逻辑。


三、复杂场景:构造函数中的异常

问题:构造函数中分配多个资源
class ResourceHolder {
public:
    ResourceHolder() {
        ptr1 = new int[100];  // 分配资源1
        ptr2 = new int[200];  // 分配资源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类
直接管理,但需谨慎
异常安全
确保所有路径释放内存

总结

策略 适用场景 优势
智能指针 常规动态内存管理 自动释放,代码简洁
RAII封装类 文件句柄、网络连接等非内存资源 统一资源生命周期管理
手动try-catch 遗留代码或特殊需求 灵活但易出错,需严格测试

通过将资源管理与对象生命周期绑定,可从根本上避免因异常导致的内存泄漏。优先使用智能指针和RAII机制,而非手动管理。

32 函数如果能抛出一个异常,那么它就会抛出这个异常,遵循这一假设。

在C++中,遵循“若函数可能抛出异常,则必须确保异常正确抛出”的假设,需要通过以下策略实现健壮的异常安全性:


一、确保异常正确抛出的关键原则

  1. 异常安全保证等级

    • 基本保证:异常发生时,对象仍处于有效状态,资源不泄漏。
    • 强保证:操作要么完全成功,要么不改变程序状态(原子性)。
    • 无抛出保证noexcept):操作绝不抛出异常。
  2. 资源管理

    • 使用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[key] = 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++库设计中,遵循“库不应自行终止程序,而应抛出异常”的原则,可确保调用者拥有完全的错误处理控制权。以下是具体实现步骤与示例:


一、设计原则

  1. 绝不调用终止函数:避免使用 std::terminateabortexit
  2. 资源安全:通过RAII管理资源,确保异常抛出时无泄漏。
  3. 异常类型明确:自定义异常类,传递详细错误信息。
  4. 文档清晰:明确标注可能抛出的异常类型及触发条件。

二、实现步骤

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++库的最佳实践。