C++如何根据用户输入为多态类型分配动态内存?关于typeid判断准确性与代码冗余的问题
我有一段C++代码,目的是根据用户选择的索引,从存储基类和派生类指针的容器中,创建对应类型的新实例并放入新容器。代码如下:
class Base; class DerivedA : public Base; class DerivedB : public Base; class DerivedC : public Base; std::vector<Base*> vector_of_base; vector_of_base.push_back(new Base); vector_of_base.push_back(new DerivedA); vector_of_base.push_back(new DerivedB); vector_of_base.push_back(new DerivedC); // 用户输入 std::vector<std::size_t> user_defined_index; std::vector<Base*> new_vector; for(auto index: user_defined_index) { // 不确定这样是否能按预期工作 Base *object = nullptr; object = vector_of_base.at(index); // 判断对象类型以分配正确的实例 if(typeid(object) == typeid(Base)) { // 分配Base的新实例(可选复制原实例的值) new_vector.push_back(new Base{object}); } else if(typeid(object) == typeid(DerivedA)) { new_vector.push_back(new DerivedA{object}); // 是否可行? } // 其余派生类的判断分支... }
现在面临两个核心问题:
- 派生类数量较多(约20-30个),上述if-else判断链会产生大量重复代码;
- 不确定当前代码的可行性:
typeid(object)是否会始终返回typeid(Base),还是能正确识别指针指向的实际派生类型?比如当object指向new DerivedA创建的实例时,typeid(object)是否会返回与typeid(DerivedA)相同的结果?
补充说明:我预先创建了一个存储所有Base及派生类实例指针的vector,这些类均重载了返回std::string的GetName()方法,方法返回的字符串会展示在图形用户界面(GUI)中。用户通过GUI选择每种类型所需的数量(可为0),我需要创建一个新的vector,其中每个元素都是通过new分配的对应类型实例,实例类型由用户在GUI中选择的数量和顺序决定。
1. 先解决typeid的可行性问题
直接给结论:你的当前代码里typeid(object)只会返回typeid(Base*)(注意是指针类型),完全达不到识别实际对象类型的效果——因为你现在的Base类没有任何虚函数,不属于多态类型。
这里要明确两个关键点:
typeid作用于指针时,只有当指针指向的类是**多态类(至少有一个虚函数)**时,才会返回指针实际指向对象的类型信息;否则,它只会返回指针本身的静态类型(也就是Base*)。- 你代码里的比较逻辑也错了:
object是Base*类型,typeid(object)得到的是指针类型的信息,而typeid(Base)是基类本身的类型,两者根本不相等。正确的写法应该是对指针解引用后再用typeid,比如typeid(*object),但前提是Base是多态类。
举个例子,把你的GetName()设为虚函数(同时加上虚析构避免内存泄漏):
class Base { public: virtual std::string GetName() const { return "Base"; } virtual ~Base() = default; };
此时typeid(*object)才能正确识别实际对象的类型:当object指向DerivedA实例时,typeid(*object) == typeid(DerivedA)会返回true。
不过即使能正确识别,用一堆if-else判断类型仍然是糟糕的设计,特别是你有20-30个派生类的情况——这完全违背了开闭原则(新增派生类时必须修改这段判断代码)。
2. 解决大量派生类的代码冗余问题
最经典也最优雅的解决方案是给基类添加一个虚克隆函数(Clone),让每个派生类重写这个函数,负责创建自身类型的新实例(可选复制原实例的状态)。
步骤1:给基类添加虚Clone方法
修改Base类,添加纯虚的Clone方法(既然所有派生类都重载了GetName(),把它设为纯虚更合理):
class Base { public: virtual std::string GetName() const = 0; virtual Base* Clone() const = 0; // 纯虚克隆函数 virtual ~Base() = default; // 必须有虚析构 };
步骤2:每个派生类实现Clone方法
以DerivedA为例,其他派生类照葫芦画瓢即可:
class DerivedA : public Base { public: std::string GetName() const override { return "DerivedA"; } // 实现克隆:创建当前类型的新实例,可选择复制当前对象的属性 DerivedA* Clone() const override { return new DerivedA(*this); // 这里假设你有合适的拷贝构造函数 } };
每个类只需要关心自己的克隆逻辑,不需要修改外部的判断代码。
步骤3:简化容器构建逻辑
现在你的循环代码可以简化到极致,完全不需要任何类型判断:
std::vector<Base*> new_vector; for(auto index: user_defined_index) { Base* original = vector_of_base.at(index); // 直接调用Clone方法,自动创建对应类型的新实例 new_vector.push_back(original->Clone()); }
不管你新增多少个派生类,这段循环代码都不需要修改,完美符合开闭原则。
额外优化:使用智能指针避免内存泄漏
如果你用C++11及以上版本,建议用std::unique_ptr代替裸指针,彻底避免手动管理内存的麻烦:
#include <memory> // 基类的Clone方法返回unique_ptr class Base { public: virtual std::string GetName() const = 0; virtual std::unique_ptr<Base> Clone() const = 0; virtual ~Base() = default; }; class DerivedA : public Base { public: std::string GetName() const override { return "DerivedA"; } std::unique_ptr<Base> Clone() const override { // make_unique是C++14及以上可用,也可以用return std::unique_ptr<DerivedA>(new DerivedA(*this)); return std::make_unique<DerivedA>(*this); } }; // 容器也改为存储unique_ptr std::vector<std::unique_ptr<Base>> vector_of_base; vector_of_base.push_back(std::make_unique<DerivedA>()); // 构建新容器 std::vector<std::unique_ptr<Base>> new_vector; for(auto index: user_defined_index) { auto& original = vector_of_base.at(index); new_vector.push_back(original->Clone()); }
内容的提问来源于stack exchange,提问作者user2138149

