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

为何抛出std::exception引用时,实际抛出基类而非派生类?

问题

我陷入了困惑,花费大量时间仍未找到问题根源。我的测试程序如下:

#include <iostream>
#include <exception>

class   SyntaxErrorException : public std::exception
{
    public:
        const char* what() const throw()
        {
            return ("SyntaxErrorException thrown");
        }
};

int main(void)
{
    SyntaxErrorException    s;
    std::exception&         e1 = s;
    std::exception&         e2 = e1;

    try
    {
        throw (s);
    }
    catch(const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << '\n';
    }
    try
    {
        throw (e1);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << '\n';
    }
    try
    {
        throw (e2);
    }
    catch(const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << '\n';
    }
    return (0);
}

程序输出如下:

Error: SyntaxErrorException thrown
Error: std::exception
Error: std::exception

我知道这与e1和e2的**对象切片(object slicing)**有关,但不清楚具体原因和过程。能否有人帮我理解抛出表达式中到底发生了什么,导致实际抛出基础std::exception对象?

我的最终需求是编写通用函数,接收std::string自定义错误消息和std::exception&自定义异常引用并抛出该异常。为何无法将自定义异常类对象按引用传递给函数并抛出?是否有其他实现方式?

解答

一、对象切片的具体原因

C++中,抛出表达式的核心逻辑是:编译器会先基于表达式的静态类型创建一个临时对象,再抛出这个临时对象。

  • 第一个throw(s):s的静态类型是SyntaxErrorException,因此创建的临时对象也是完整的子类实例。catch时用std::exception&接收,多态机制生效,调用子类的what(),输出正确。
  • 第二个throw(e1):e1的静态类型是std::exception&,编译器只会按照基类std::exception的结构创建临时对象——子类SyntaxErrorException的多态信息(如虚函数表指针)会被丢弃,最终抛出的是纯基类对象,调用的自然是基类的what()。
  • 第三个throw(e2)逻辑完全相同,e2的静态类型同样是std::exception&,触发切片后抛出基类对象。

简言之:抛出引用时,编译器只认引用的静态类型,而非它实际指向的动态类型,这就是切片发生的核心原因。

二、正确抛出自定义异常引用的实现方式

直接抛引用必然触发切片,要保留多态特性,可通过以下几种方式实现:

方法1:抛出指针(需注意内存管理)

让自定义异常实现克隆方法,在通用函数中克隆子类对象后抛出指针:

#include <iostream>
#include <exception>
#include <string>

class SyntaxErrorException : public std::exception
{
private:
    std::string msg;
public:
    explicit SyntaxErrorException(const std::string& m) : msg(m) {}
    const char* what() const noexcept override
    {
        return msg.c_str();
    }
    // 克隆方法,返回子类指针
    SyntaxErrorException* clone() const
    {
        return new SyntaxErrorException(msg);
    }
    // 可选:添加修改消息的方法
    void appendMsg(const std::string& extra)
    {
        msg += " | " + extra;
    }
};

void throwCustomException(const std::exception& e, const std::string& customMsg)
{
    // 动态判断异常类型,调用对应克隆方法
    if (auto* se = const_cast<SyntaxErrorException*>(dynamic_cast<const SyntaxErrorException*>(&e)))
    {
        se->appendMsg(customMsg);
        throw se->clone();
    }
    // 可扩展其他异常类型的处理逻辑
}

int main()
{
    try
    {
        SyntaxErrorException se("原始语法错误");
        throwCustomException(se, "自定义附加信息");
    }
    catch(const SyntaxErrorException* e)
    {
        std::cerr << "Error: " << e->what() << '\n';
        delete e; // 手动释放内存
    }
    catch(const std::exception* e)
    {
        std::cerr << "Error: " << e->what() << '\n';
        delete e;
    }
    return 0;
}

方法2:使用智能指针避免内存泄漏

用std::unique_ptr托管异常指针,无需手动释放:

#include <memory>

// 修改克隆方法返回智能指针
std::unique_ptr<SyntaxErrorException> SyntaxErrorException::clone() const
{
    return std::make_unique<SyntaxErrorException>(msg);
}

void throwCustomException(const std::exception& e, const std::string& customMsg)
{
    if (auto* se = const_cast<SyntaxErrorException*>(dynamic_cast<const SyntaxErrorException*>(&e)))
    {
        se->appendMsg(customMsg);
        throw se->clone();
    }
}

// catch时直接接收智能指针
catch(const std::unique_ptr<SyntaxErrorException>& e)
{
    std::cerr << "Error: " << e->what() << '\n';
}

方法3:使用std::exception_ptr(C++11及以上)

std::exception_ptr是C++标准提供的安全传递异常的工具,适合通用场景:

#include <exception>
#include <string>

void throwCustomException(std::exception_ptr ePtr, const std::string& customMsg)
{
    try
    {
        std::rethrow_exception(ePtr);
    }
    catch(SyntaxErrorException& e)
    {
        e.appendMsg(customMsg);
        throw; // 重新抛出修改后的异常
    }
    // 其他异常类型处理逻辑
}

int main()
{
    try
    {
        throw SyntaxErrorException("原始语法错误");
    }
    catch(...)
    {
        throwCustomException(std::current_exception(), "自定义附加信息");
    }
    return 0;
}

总结

  • 抛出引用时,编译器按静态类型创建临时对象,导致对象切片;
  • 要保留多态特性,必须抛出指针(或智能指针),或使用std::exception_ptr;
  • 自定义异常实现clone方法,能更方便地在通用函数中复制并抛出子类对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:07:50