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

C++中std::exception复制到结构体成员失效的原因及解决方法

问题原因与解决方案

问题原因

这是C++中典型的**对象切片(Object Slicing)**问题:
std::logic_error是std::exception的派生类,当执行mException = e;时,尽管e实际引用的是std::logic_error对象,但赋值操作只会复制基类std::exception的部分内容,派生类独有的数据(比如你传入的错误信息字符串)会被“切掉”。最终mException只是一个普通的std::exception对象,调用what()自然返回基类的默认描述“std::exception”。

修改方案

方案一:使用std::exception_ptr存储异常(推荐)

std::exception_ptr是C++11引入的类型,专门用于安全传递和存储异常对象,彻底避免切片问题。修改后的代码如下:

#include <string>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <exception>

enum class Severity
{
  TRACE,
  DEBUG,
  INFO,
  WARN,
  ERROR,
  FATAL,
};

struct LogRecord
{
  const Severity          mSeverity{};
  const std::string       mTimestamp{};
  const char* const       mFunc{};
  const size_t            mLine{};
  const char* const       mFile{};
  std::string             mMessage;
  std::exception_ptr      mExceptionPtr{};

  LogRecord& operator<<(const std::exception& e)
  {
    mExceptionPtr = std::make_exception_ptr(e);
    return *this;
  }

  template <typename T>
  LogRecord& operator<<(const T& msg)
  {
    std::ostringstream ss;
    ss << msg;
    mMessage += ss.str();
    return *this;
  }
};

void throw_exception()
{
  throw std::logic_error("Logic error");
}

int main()
{
  LogRecord l{};
  try
  {
    throw_exception();
  }
  catch(std::exception& e)
  {
    l << "Test" << e;
    if (l.mExceptionPtr) {
      try {
        std::rethrow_exception(l.mExceptionPtr);
      } catch(const std::exception& ex) {
        std::cout << l.mMessage << ": " << ex.what() << std::endl;
      }
    }
  }
}

运行后会输出预期的Test: Logic error,因为std::make_exception_ptr完整保留了原始异常对象的类型和数据,后续通过std::rethrow_exception重新抛出后,能正确获取派生类的what()结果。

方案二:使用智能指针存储动态分配的异常对象

如果需要兼容C++11之前的版本,可以用智能指针管理动态分配的异常对象(需手动处理不同派生类的复制):

#include <string>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <memory>

enum class Severity
{
  TRACE,
  DEBUG,
  INFO,
  WARN,
  ERROR,
  FATAL,
};

struct LogRecord
{
  const Severity          mSeverity{};
  const std::string       mTimestamp{};
  const char* const       mFunc{};
  const size_t            mLine{};
  const char* const       mFile{};
  std::string             mMessage;
  std::unique_ptr<std::exception> mException{};

  LogRecord& operator<<(const std::exception& e)
  {
    // 针对已知的派生类进行类型转换和复制
    if (const auto* logic_err = dynamic_cast<const std::logic_error*>(&e)) {
      mException = std::make_unique<std::logic_error>(*logic_err);
    } else if (const auto* runtime_err = dynamic_cast<const std::runtime_error*>(&e)) {
      mException = std::make_unique<std::runtime_error>(*runtime_err);
    } else {
      mException = std::make_unique<std::exception>(e);
    }
    return *this;
  }

  template <typename T>
  LogRecord& operator<<(const T& msg)
  {
    std::ostringstream ss;
    ss << msg;
    mMessage += ss.str();
    return *this;
  }
};

void throw_exception()
{
  throw std::logic_error("Logic error");
}

int main()
{
  LogRecord l{};
  try
  {
    throw_exception();
  }
  catch(std::exception& e)
  {
    l << "Test" << e;
    if (l.mException) {
      std::cout << l.mMessage << ": " << l.mException->what() << std::endl;
    }
  }
}

这种方式需要逐个处理可能的异常派生类,扩展性不如std::exception_ptr方案,仅作为兼容旧版本的备选。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:35:18