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
相关产品推荐
相关产品推荐

