如何在类中正确使用std::list存储抽象类派生对象?
问题分析与解决方案
问题背景
示例代码:
#include <list> #include <cstdlib> #include <cstdio> class cont; class elem { public: virtual void do_something(int *buff) = 0; }; class myelem1 : public elem { public: virtual void do_something(int *buff) { buff[1] = 42; } }; class myelem2 : public elem { public: virtual void do_something(int *buff) { buff[2] = 33; } }; class cont { private: int *buff, size; std::list<elem> sub; public: cont(int size) : buff((int*)malloc(size*sizeof(int))), size(size) {} ~cont() { if (buff) free(buff); } void add_worker(const elem &e) {sub.push_back(e);} void work() { for (elem & e : sub) e.do_something(buff); } void print() { for (int i=0; i<size; i++) printf("%03d ", buff[i]); printf("\n"); } }; int main() { cont c(10); c.add_worker(myelem1()); c.add_worker(myelem2()); c.work(); c.print(); }
需求总结:
cont类内部管理待填充的数据集- 多个
elem抽象类的派生类,各自负责填充数据集的特定部分 - 编译时无法确定使用哪些派生类,需运行时按需创建
- 实际数据集并非简单数组
遇到的问题:
- 原代码编译失败,报错:
/usr/include/c++/12/ext/aligned_buffer.h:54:25: error: cannot declare field ‘__gnu_cxx::__aligned_membuf<elem>::_Tp2::_M_t’ to be of abstract type ‘elem’
- 将
elem的纯虚函数改为空实现后,代码可编译,但cont::buff未被填充。
核心问题解析
1. 编译失败的原因
elem是抽象类(包含纯虚函数),C++不允许直接实例化抽象类。而cont类中用std::list<elem> sub;存储的是elem对象,当尝试将派生类对象放入列表时,需要创建elem类型的实例,这违反了抽象类不能实例化的规则,因此编译报错。
2. 编译通过但无效果的原因
把纯虚函数改成空实现后,elem不再是抽象类,但出现了对象切片问题:当你把myelem1/myelem2对象传递给add_worker(参数是const elem&)并调用push_back时,只会复制基类elem的部分,派生类特有的虚函数表等信息会被“切掉”。后续遍历列表调用do_something时,实际调用的是基类elem的空实现,而非派生类的重载方法,因此buff没有被填充。
解决方案:使用指针/智能指针实现多态
C++的多态必须通过指针或引用来实现,因此需要将容器改为存储基类指针。为了避免手动管理内存泄漏,推荐使用智能指针(如std::unique_ptr)。
修改后的代码示例:
#include <list> #include <memory> #include <cstdlib> #include <cstdio> #include <cstring> class elem { public: virtual void do_something(int *buff) = 0; // 抽象类必须声明虚析构函数,避免派生类对象内存泄漏 virtual ~elem() = default; }; class myelem1 : public elem { public: void do_something(int *buff) override { buff[1] = 42; } }; class myelem2 : public elem { public: void do_something(int *buff) override { buff[2] = 33; } }; class cont { private: int *buff, size; // 存储智能指针,自动管理内存 std::list<std::unique_ptr<elem>> sub; public: cont(int size) : buff(static_cast<int*>(malloc(size*sizeof(int)))), size(size) { // 初始化buff为0,避免垃圾值干扰结果 if (buff) memset(buff, 0, size*sizeof(int)); } ~cont() { if (buff) free(buff); } // 接收unique_ptr并转移所有权 void add_worker(std::unique_ptr<elem> e) { sub.push_back(std::move(e)); } void work() { for (auto & e : sub) { e->do_something(buff); } } void print() { for (int i=0; i<size; i++) printf("%03d ", buff[i]); printf("\n"); } }; int main() { cont c(10); // 创建派生类对象的智能指针,转移给cont c.add_worker(std::make_unique<myelem1>()); c.add_worker(std::make_unique<myelem2>()); c.work(); c.print(); }
关键修改点:
- 给抽象类
elem添加虚析构函数:确保销毁基类指针时,派生类的析构函数能被正确调用,避免内存泄漏。 - 将容器改为
std::list<std::unique_ptr<elem>>:存储基类智能指针,支持多态,同时自动管理内存。 - 修改
add_worker接口:接收std::unique_ptr<elem>并通过std::move转移所有权,避免拷贝。 - 初始化
buff为0:避免打印垃圾值,方便观察填充结果。
内容的提问来源于stack exchange,提问作者ZioByte
相关产品推荐
相关产品推荐

