含shared_ptr的联合体:改写类A构造析构以适配vector及emplace_back
解决方案
核心问题说明
原来的memset初始化方式会直接破坏std::shared_ptr的内部结构(比如引用计数),而且当union包含非平凡类型(比如带自定义构造/析构的std::shared_ptr)时,C++无法自动管理union成员的生命周期,必须手动跟踪活跃成员并控制构造、析构逻辑。
具体修改步骤
1. 添加判别式跟踪活跃成员
在类A中增加一个标记,用来记录当前union中哪个成员处于活跃状态(同一时间union只能有一个成员有效)。
2. 重写默认构造函数
放弃memset,显式构造默认激活的成员(比如默认激活part1,对齐原来memset后b=0的效果)。
3. 实现析构函数
根据判别式,手动调用活跃成员的析构函数(std::shared_ptr必须手动析构才能正确释放资源)。
4. 补充拷贝/移动语义(可选但必要)
要存入vector,类A必须支持拷贝或移动操作,否则vector扩容时会触发默认拷贝,破坏shared_ptr的引用计数。
修改后的完整代码
#include <memory> #include <vector> struct B { int b; }; struct D { std::shared_ptr<int> d = nullptr; }; class A { public: // 标记当前活跃的union成员 enum ActiveMember { PART1, PART3 }; ActiveMember active; union { B part1; D part3; } parts; // 默认构造:激活part1并初始化b为0 A() : active(PART1) { // 用placement new显式构造B成员(平凡类型也可直接赋值,此写法更规范) new (&parts.part1) B{0}; } // 析构:根据活跃成员调用对应析构函数 ~A() { if (active == PART3) { // 手动调用D的析构,释放shared_ptr资源 parts.part3.~D(); } // B是平凡类型,无需手动析构 } // 拷贝构造函数 A(const A& other) : active(other.active) { if (active == PART1) { new (&parts.part1) B(other.parts.part1); } else { new (&parts.part3) D(other.parts.part3); } } // 拷贝赋值运算符 A& operator=(const A& other) { if (this != &other) { // 先析构当前活跃成员 if (active == PART3) { parts.part3.~D(); } // 构造新的活跃成员 active = other.active; if (active == PART1) { new (&parts.part1) B(other.parts.part1); } else { new (&parts.part3) D(other.parts.part3); } } return *this; } // 移动构造函数 A(A&& other) noexcept : active(other.active) { if (active == PART1) { new (&parts.part1) B(std::move(other.parts.part1)); } else { new (&parts.part3) D(std::move(other.parts.part3)); } // 重置原对象状态,避免重复析构 other.active = PART1; } // 移动赋值运算符 A& operator=(A&& other) noexcept { if (this != &other) { // 先析构当前活跃成员 if (active == PART3) { parts.part3.~D(); } // 移动构造新成员 active = other.active; if (active == PART1) { new (&parts.part1) B(std::move(other.parts.part1)); } else { new (&parts.part3) D(std::move(other.parts.part3)); } // 重置原对象状态 other.active = PART1; } return *this; } }; // 使用示例 int main() { std::vector<A> vec; vec.emplace_back(); // 可正常调用默认构造函数 return 0; }
关键注意点
- 判别式不可少:C++编译器无法自动跟踪union的活跃成员,必须手动标记,否则析构时会导致资源泄漏或非法访问。
- 绝对不能用memset:
memset会直接覆盖std::shared_ptr的内部引用计数等数据,引发严重错误。 - 显式构造/析构:对于union中的非平凡类型,必须用
placement new显式构造,析构时也要手动调用析构函数。
内容的提问来源于stack exchange,提问作者annaa-ka
相关产品推荐
相关产品推荐

