如何推导容器中存储的各函数(派生类相关)的返回类型
解决容器中存储的函数返回实际派生类型的问题
你的核心问题是对象切片导致的:当你把返回Derived1/Derived2的lambda存入std::vector<std::function<Base(void)>>时,lambda返回的派生类对象会被自动切割成Base类型——也就是说,实际存储的函数返回的就是Base对象,派生类的类型信息已经丢失了,所以decltype(fn)::result_type只能得到静态类型Base,运行时也无法恢复派生类信息。
要解决这个问题,你需要避免对象切片,同时保留运行时的类型信息,具体方案如下:
步骤1:改用指针/智能指针作为返回值
让容器中的函数返回std::unique_ptr<Base>(或其他智能指针),这样不会发生对象切片,指针会指向实际的派生类对象,保留完整的类型信息。
步骤2:运行时识别实际类型
通过RTTI(运行时类型信息)的dynamic_cast或typeid来判断指针指向的实际对象类型,进而实现你需要的new Derived1这类操作。
修改后的完整代码
#include <random> #include <vector> #include <functional> #include <memory> #include <typeinfo> using namespace std; class Base { public: virtual ~Base() = default; // 必须添加虚析构,确保智能指针正确销毁派生类对象 virtual void test() { } }; class Derived1 : public Base { public: void test() override { } }; class Derived2 : public Base { public: void test() override { } }; int main () { // 容器存储返回unique_ptr<Base>的函数 std::vector<std::function<std::unique_ptr<Base>()>> b; b.push_back([]() { return make_unique<Derived1>(); }); b.push_back([]() { return make_unique<Derived2>(); }); b.push_back([]() { return make_unique<Base>(); }); std::shuffle(b.begin(), b.end(), std::default_random_engine {}); for (auto& fn : b) { auto obj_ptr = fn(); // 方法1:用dynamic_cast判断类型(安全,需虚函数支持) if (Derived1* d1_ptr = dynamic_cast<Derived1*>(obj_ptr.get())) { // 确认是Derived1类型,可创建新实例 auto new_d1 = make_unique<Derived1>(); // 这里可以对new_d1执行Derived1的专属操作 } else if (Derived2* d2_ptr = dynamic_cast<Derived2*>(obj_ptr.get())) { auto new_d2 = make_unique<Derived2>(); // 执行Derived2的专属操作 } else if (typeid(*obj_ptr) == typeid(Base)) { auto new_base = make_unique<Base>(); // 执行Base的操作 } // 方法2:用typeid直接获取类型信息 const auto& actual_type = typeid(*obj_ptr); if (actual_type == typeid(Derived1)) { // 处理Derived1 } else if (actual_type == typeid(Derived2)) { // 处理Derived2 } } }
关键注意事项
- 虚析构函数:Base类必须声明虚析构,否则智能指针销毁对象时只会调用Base的析构函数,导致派生类的资源泄漏。
- 避免对象切片:只有通过指针/引用传递多态对象,才能保留派生类的类型信息,值传递必然会切片。
- RTTI的限制:
dynamic_cast要求类有至少一个虚函数(你的代码中Base已经有test虚函数,满足条件),如果转换失败会返回nullptr;typeid返回的std::type_info对象可以直接比较,但无法直接用来创建新对象,仍需配合条件分支手动处理。
关于你尝试的std::any和std::variant:如果用它们存储不同类型的函数(比如function<Derived1()>、function<Derived2()>),容器的类型会变得非常复杂(比如vector<variant<function<Derived1()>, function<Derived2()>, function<Base()>>>),不仅扩展性差(新增派生类需要修改variant的模板参数),而且处理每个元素时需要遍历所有可能的类型分支,远不如多态指针的方案简洁灵活。
内容的提问来源于stack exchange,提问作者Kitiara
相关产品推荐
相关产品推荐

