C++中使用union操作时是否需要调用std::launder?
#include <iostream> #include <string> union U { std::string s; U() { new(&s) std::string; } void Set(std::string new_s) { s.~basic_string(); new(&s) std::string(std::move(new_s)); } ~U() { s.~basic_string(); } }; int main() { U u; u.Set("foo"); std::cout << u.s << '\n'; // Do I need `std::launder` here? }
问题
我知道如果用字节数组替代union,必须通过std::launder(&s)来访问字符串,但在union的场景下是否需要?常识判断似乎不需要,但C++标准中哪里明确规定union无需使用std::launder?
回答
不需要使用std::launder,核心依据来自C++标准对union的特殊规则:
活跃成员的访问权限
C++标准规定,union同一时间仅能存在一个活跃成员。在你的Set函数中,先销毁原有std::string,再通过placement new在&s位置创建新对象后,这个新的std::string就成为了union U的活跃成员。此时直接通过u.s访问该成员是完全合法的,标准明确允许这种访问方式。与字节数组的本质差异
字节数组场景中,原数组是一个独立对象,在其存储位置创建新类型对象后,数组名仍关联原数组类型,存在类型别名冲突,必须用std::launder获取新对象的有效指针。而union的成员名s本身就绑定std::string类型,当该类型对象成为活跃成员时,u.s直接指向这个新创建的对象,不存在类型不匹配问题,自然无需std::launder。标准条款支撑
根据C++标准[class.union]章节:当union的某个成员对象被创建后,该成员即成为union的活跃成员;后续通过union成员名访问活跃成员的行为是被允许的,无需额外的内存清洗操作。
另外补充:你的代码中手动调用std::string的构造和析构函数是正确的——union的默认构造/析构不会处理非平凡类型成员,必须手动管理其生命周期。
内容的提问来源于stack exchange,提问作者HolyBlackCat

