std::any同类型重复赋值的性能问题及优化方案合理性验证
你的核心思路——复用std::any已有的同类型存储对象、避免重复内存分配——是完全合理的,测试数据也证明了性能提升的有效性。但原始实现存在几个关键隐患和未覆盖的场景,具体如下:
1. 未检查std::any_cast返回值导致未定义行为
原始代码通过typeid(T)判断类型匹配,但typeid会忽略顶层cv限定符(比如const int和int的typeid结果相同)。如果std::any存储的是const T类型,std::any_cast<T>(&p_out)会返回nullptr,此时解引用指针会直接触发未定义行为(崩溃或其他异常)。
修复方案
直接通过std::any_cast的返回值判断类型匹配,而非依赖typeid:
template<typename T> void set_any_value_with_shortcut(std::any & p_out, T const & p_value) { if (auto ptr = std::any_cast<T>(&p_out)) { *ptr = p_value; return; } p_out = p_value; }
该方式会准确判断存储类型是否与T完全匹配(包括cv限定符),彻底避免空指针解引用风险。
2. 异常安全缺失
标准的std::any::operator=保证异常安全:如果赋值操作抛出异常,std::any会保持原有状态不变。但直接修改内部对象的赋值操作若抛出异常,会导致std::any中的对象处于无效状态(部分修改完成、部分未完成)。
优化方向
若需要保证异常安全,可以针对noexcept的赋值操作直接修改,否则改用异常安全的交换方式:
template<typename T> void set_any_value_with_shortcut(std::any & p_out, T const & p_value) { if (auto ptr = std::any_cast<T>(&p_out)) { if constexpr (std::is_nothrow_assignable_v<T, const T&>) { *ptr = p_value; } else { // 用swap保证异常安全(swap通常为noexcept) T temp = p_value; std::swap(*ptr, temp); } return; } p_out = p_value; }
3. 未覆盖引用类型场景
如果std::any存储的是std::reference_wrapper<T>(引用语义),原始代码会因typeid(T)与typeid(std::reference_wrapper<T>)不匹配,执行重新赋值操作——这不仅无法直接修改引用指向的值,还会将std::any的存储类型从引用转为值类型,违背预期。
扩展支持方案
若需要兼容引用类型,可扩展函数处理std::reference_wrapper:
template<typename T> void set_any_value_with_shortcut(std::any & p_out, T && p_value) { using U = std::decay_t<T>; // 处理值类型 if (auto ptr = std::any_cast<U>(&p_out)) { *ptr = std::forward<T>(p_value); return; } // 处理引用类型(std::reference_wrapper<U>) if (auto ref_ptr = std::any_cast<std::reference_wrapper<U>>(&p_out)) { ref_ptr->get() = std::forward<T>(p_value); return; } p_out = std::forward<T>(p_value); }
总结
你的优化思路在同类型重复赋值场景下的性能收益是显著的,只需解决上述隐患并按需扩展场景,就能得到更健壮的实现。
内容的提问来源于stack exchange,提问作者0xbaadf00d

