C++继承实例无法放入共用体?求无动态分配优化方案
无动态分配的多态+内存共享方案优化
问题说明
程序分为配置阶段与执行阶段:配置阶段选择具体的函数实现,执行阶段循环调用该函数。为避免执行阶段每次循环都通过switch/case判断逻辑,采用继承+虚函数的多态方案,让子类实现基类虚函数,借助虚表直接调用。
同时需要优化内存占用:由于同一时间仅使用一种子类实现,希望所有子类的内部状态共享同一块内存空间。但带虚函数的子类拥有非平凡构造函数,无法直接放入union(编译器报错:error: union member ‘main()::::a’ with non-trivial ‘ChildA::ChildA()’)。
目前的解决方案是在类外定义包含共用体的结构体,将其指针传入类实例,但该方案不够优雅,现寻求更优的无动态内存分配方案。
问题代码
#include <stdio.h> using namespace std; class Base{ public: virtual void doprint() = 0; }; class ChildA : public Base{ public: virtual void doprint(){ printf("I am A : %d",foo); }; int foo; }; class ChildB : public Base{ public: virtual void doprint(){ printf("I am B : %u", bar); }; unsigned int bar; }; int main() { // 直接放子类会报错 union{ ChildA a; ChildB b; } u; // 配置阶段 u.a.foo = -10; Base *pbase = &u.a; // 执行阶段 pbase->doprint(); return 0; }
现有解决方案代码
#include <stdio.h> using namespace std; union InternalData{ struct { int foo; } data_for_a; struct { unsigned int bar; } data_for_b; }; class Base{ public: void init(InternalData *data) { m_data = data; } virtual void doprint() = 0; protected: InternalData* m_data; }; class ChildA : public Base{ public: virtual void doprint(){ printf("I am A : %d", m_data->data_for_a.foo); }; }; class ChildB : public Base{ public: virtual void doprint(){ printf("I am B : %u", m_data->data_for_b.bar); }; }; int main() { ChildA a; ChildB b; InternalData internal_data; // 配置阶段 internal_data.data_for_a.foo = -10; a.init(&internal_data); Base *pbase = &a; // 执行阶段 pbase->doprint(); return 0; }
优化方案
方案1:Placement New + Union 手动管理内存
利用placement new在union的原始内存上构造子类实例,既实现内存共享,又保留多态特性,全程无堆内存分配。
#include <stdio.h> #include <new> // 引入placement new using namespace std; class Base { public: virtual void doprint() = 0; virtual ~Base() = default; // 虚析构,确保子类资源正确释放 }; class ChildA : public Base { public: ChildA(int val) : foo(val) {} void doprint() override { printf("I am A : %d\n", foo); } int foo; }; class ChildB : public Base { public: ChildB(unsigned int val) : bar(val) {} void doprint() override { printf("I am B : %u\n", bar); } unsigned int bar; }; // 定义union,内存大小取最大子类的尺寸,alignas保证对齐 union PolyUnion { alignas(ChildA) char buf_A[sizeof(ChildA)]; alignas(ChildB) char buf_B[sizeof(ChildB)]; }; int main() { PolyUnion u; Base* pbase = nullptr; // 配置阶段:在union内存上构造ChildA pbase = new(u.buf_A) ChildA(-10); // 执行阶段:调用虚函数 pbase->doprint(); // 手动调用析构函数(placement new不会自动销毁对象) pbase->~Base(); // 切换实现:复用同一块内存构造ChildB pbase = new(u.buf_B) ChildB(20); pbase->doprint(); pbase->~Base(); return 0; }
关键说明:
alignas确保union的内存对齐符合子类的要求,避免内存访问错误- placement new直接在栈上的union内存中构造对象,完全无动态分配
- 必须手动调用析构函数,因为placement new不会自动触发对象销毁
- 同一时间仅存在一个子类实例,完全共享union的内存空间
方案2:C++17 std::variant 类型安全的内存共享
如果项目支持C++17及以上,std::variant是标准库提供的类型安全联合体,原生支持带非平凡构造函数的类型,无需手动管理内存:
#include <stdio.h> #include <variant> using namespace std; class Base { public: virtual void doprint() = 0; virtual ~Base() = default; }; class ChildA : public Base { public: ChildA(int val) : foo(val) {} void doprint() override { printf("I am A : %d\n", foo); } int foo; }; class ChildB : public Base { public: ChildB(unsigned int val) : bar(val) {} void doprint() override { printf("I am B : %u\n", bar); } unsigned int bar; }; int main() { variant<ChildA, ChildB> var; // 配置阶段:在variant内存中构造ChildA var.emplace<ChildA>(-10); // 执行阶段:获取基类指针调用虚函数 Base* pbase = &get<ChildA>(var); pbase->doprint(); // 切换实现:复用variant内存构造ChildB var.emplace<ChildB>(20); pbase = &get<ChildB>(var); pbase->doprint(); return 0; }
关键说明:
std::variant自动处理对象的构造与销毁,无需手动调用析构emplace方法直接在variant的栈内存中构造对象,无动态分配- 类型安全,避免了手动操作union内存的风险
- 通过
std::get获取具体子类实例,转为基类指针即可实现多态调用
内容的提问来源于stack exchange,提问作者user2302957
相关产品推荐
相关产品推荐

