You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

实现安全的C++自定义异常:方案安全性与优化咨询

自定义异常类的安全性、可靠性分析及优化建议

问题背景

我最初在CodeReview发布此问题,后被引导至此更合适的平台。

我尝试设计继承内置异常的自定义异常类,简单场景下使用如下实现:

class SimpleCustomException : public std::runtime_error
{
public:
    using std::runtime_error::runtime_error;
};

该实现本质是std::runtime_error的包装,可正常工作。但当需要传递如std::string这类任意额外数据时,因异常需可复制且栈展开时不可抛出异常(否则程序会终止),直接使用std::string可能在复制时抛出std::bad_alloc,因此我设计了基于std::shared_ptr的实现:

#include <iostream>
#include <string>
#include <memory>

class CustomException : public std::runtime_error
{
public:
    CustomException(const std::string &what, const std::string &err_data) noexcept
        : std::runtime_error(what)
    {
        /* We try to allocate the data, as fallback just pass nullptr */
        try {
            this->error_data = std::make_shared<std::string>(err_data);
        } catch (const std::bad_alloc &e) {
            this->error_data = nullptr;
        }
    }

    std::shared_ptr<std::string> what_data() const noexcept
    {
        return error_data;
    }

private:
    std::shared_ptr<std::string> error_data;
};

int main()
{
    try {
        throw CustomException("HELLO", "WORLD");
    } catch (const CustomException &e) {
        std::string message = e.what();
        std::string extra_data;
        if (e.what_data()) {
            extra_data = *e.what_data();
        }
        std::cout << "Caught exception with message: " << message << std::endl;
        std::cout << "And extra data: " << extra_data << std::endl;
    }

    return 0;
}

我想了解该具体实现的安全性、可靠性如何,以及是否存在优化空间。


安全性与可靠性分析

核心优点

  • 符合异常构造的noexcept要求:构造函数标记为noexcept,且内部捕获了std::bad_alloc,确保内存分配失败时不会抛出新异常,避免栈展开阶段程序因异常嵌套直接终止。
  • 异常对象可安全复制:std::shared_ptr的复制操作是原子且不会抛异常的,因此CustomException的默认复制/移动行为完全安全,不会在栈展开时触发问题。
  • 降级处理合理:内存分配失败时自动将error_data设为nullptr,捕获方可通过空指针判断做降级处理,不会因为额外数据分配失败导致主异常逻辑失效。

潜在风险

  • 基类构造的隐式异常风险:你的构造函数标记了noexcept,但基类std::runtime_error接受const std::string&的构造函数可能抛出std::bad_alloc(它需要复制字符串到内部存储),这会直接违背noexcept承诺,触发程序终止。
  • 空指针访问风险:如果捕获方直接解引用what_data()返回的指针而不做空指针检查,会触发未定义行为。虽然示例代码做了检查,但实际使用中需要确保所有调用方都遵守这个规则。
  • shared_ptr的轻微开销:shared_ptr的复制依赖原子引用计数操作,对于高频触发的异常场景,这个开销虽然不大,但相比直接存储字符串会有额外消耗。

优化空间

1. 修复基类构造的noexcept问题

std::runtime_error接受const char*的构造函数是noexcept的(无需分配内存,直接存储指针),可以优先使用这个重载避免基类构造抛异常:

// 优先提供const char*版本,确保构造安全
CustomException(const char* what, const std::string &err_data) noexcept
    : std::runtime_error(what)
{
    try {
        error_data = std::make_shared<std::string>(err_data);
    } catch (const std::bad_alloc&) {
        error_data = nullptr;
    }
}

// 字符串版本明确标记为非noexcept,提示调用方可能抛异常
CustomException(const std::string &what, const std::string &err_data) noexcept(false)
    : std::runtime_error(what)
{
    // 原有分配逻辑
}

如果必须接受std::string作为what参数,也可以用静态默认字符串做降级:

CustomException(const std::string &what, const std::string &err_data) noexcept
    : std::runtime_error([]() noexcept {
        // c_str()不会抛异常,若字符串内部存储分配失败(极端情况),返回默认值
        try {
            return what.c_str();
        } catch (...) {
            return "Unknown error";
        }
    }())
{
    // 原有分配逻辑
}

2. 改用std::unique_ptr减少开销

如果不需要在多个异常副本间共享额外数据,std::unique_ptr比std::shared_ptr更轻量(无原子引用计数开销):

class CustomException : public std::runtime_error
{
public:
    CustomException(const char* what, const std::string &err_data) noexcept
        : std::runtime_error(what)
    {
        try {
            error_data = std::make_unique<std::string>(err_data);
        } catch (const std::bad_alloc&) {
            error_data = nullptr;
        }
    }

    // 返回const指针,避免所有权转移,调用方直接访问即可
    const std::string* what_data() const noexcept
    {
        return error_data.get();
    }

private:
    std::unique_ptr<std::string> error_data;
};

3. 固定大小数组避免动态分配

如果额外数据长度有限,可以用固定大小字符数组完全消除内存分配风险:

class CustomException : public std::runtime_error
{
public:
    static constexpr size_t MAX_DATA_LEN = 256;

    CustomException(const char* what, const std::string &err_data) noexcept
        : std::runtime_error(what)
    {
        std::strncpy(data_buf, err_data.c_str(), MAX_DATA_LEN - 1);
        data_buf[MAX_DATA_LEN - 1] = '\0';
    }

    const char* what_data() const noexcept
    {
        return data_buf;
    }

private:
    char data_buf[MAX_DATA_LEN];
};

4. 简化调用方接口

提供直接返回字符串的接口,内部处理空指针情况,减少调用方代码量:

std::string get_error_data() const noexcept
{
    try {
        return error_data ? *error_data : std::string();
    } catch (...) {
        return std::string();
    }
}

内容的提问来源于stack exchange,提问作者Jakub

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:47:38