如何为派生类vector调用对应方法?寻求无类型转换的实现方案
咱们先搞清楚为啥会报这个错:C++的容器不支持协变——哪怕Boo和Doo都继承自Foo,std::vector<std::unique_ptr<Foo>>和std::vector<std::unique_ptr<Boo>>也是完全独立的类型,两者之间没有任何隐式转换或继承关系,所以你没法直接把前者绑定到后者的引用上。
下面根据你的实际场景,给你几种无需手动强制类型转换的实现方式:
场景1:每个Map条目的Vector里只有单一派生类实例
如果某个Key对应的vector里全是Boo,另一个Key对应的全是Doo,那可以把Map的value类型改成std::variant,直接存储不同类型的vector,让编译器帮你自动分发调用:
#include <variant> // 调整Map的类型,用variant存储两种vector std::map<Key, std::variant<std::vector<std::unique_ptr<Boo>>, std::vector<std::unique_ptr<Doo>>>> myMap; // 遍历的时候用std::visit自动匹配对应的doSomething重载 for (auto& [key, vecVar] : myMap) { std::visit([](auto& vec) { doSomething(vec); }, vecVar); }
这里std::visit会根据variant当前实际存储的vector类型,自动调用对应的doSomething函数,完全不需要手动做类型判断或转换。
场景2:Vector里混合了Boo和Doo的实例
如果你的vector里同时存在两种派生类的对象,那可以先逐个检查元素类型,把它们分类到临时容器后再调用对应函数:
方式A:不转移所有权(推荐,如果doSomething允许修改参数)
首先调整doSomething的参数类型,改成接受原始指针的vector:
// 修改doSomething的参数为原始指针的vector void doSomething(std::vector<Boo*>& booList) { /* 你的逻辑 */ } void doSomething(std::vector<Doo*>& dooList) { /* 你的逻辑 */ }
然后遍历收集类型匹配的指针:
for (auto& [key, fooVec] : myMap) { std::vector<Boo*> booTemp; std::vector<Doo*> dooTemp; for (auto& fooPtr : fooVec) { // dynamic_cast安全检查类型(需要Foo有虚函数,你用多态的话肯定已经有了) if (auto booPtr = dynamic_cast<Boo*>(fooPtr.get())) { booTemp.push_back(booPtr); } else if (auto dooPtr = dynamic_cast<Doo*>(fooPtr.get())) { dooTemp.push_back(dooPtr); } } // 调用对应函数 if (!booTemp.empty()) doSomething(booTemp); if (!dooTemp.empty()) doSomething(dooTemp); }
这种方式不会改变原Map中元素的所有权,只是临时收集指针,安全且灵活。
方式B:转移所有权(如果之后不需要原vector的元素)
如果允许转移原vector中元素的所有权,也可以直接把unique_ptr转换类型后放到临时容器:
for (auto& [key, fooVec] : myMap) { std::vector<std::unique_ptr<Boo>> booTemp; std::vector<std::unique_ptr<Doo>> dooTemp; auto it = fooVec.begin(); while (it != fooVec.end()) { if (auto booPtr = dynamic_cast<Boo*>(it->get())) { // 转移所有权到临时容器 booTemp.push_back(std::unique_ptr<Boo>(booPtr)); it = fooVec.erase(it); // 从原vector中移除该元素 } else if (auto dooPtr = dynamic_cast<Doo*>(it->get())) { dooTemp.push_back(std::unique_ptr<Doo>(dooPtr)); it = fooVec.erase(it); } else { ++it; } } doSomething(booTemp); doSomething(dooTemp); }
注意这种方式会清空原vector中匹配的元素,适合你处理完就不再需要这些元素的场景。
最优解:调整设计,用多态替代类型分支
其实最符合C++多态设计思想的方式,是把doSomething的逻辑直接放到基类的虚函数里:
class Foo { public: virtual ~Foo() = default; // 必须有虚析构,保证派生类对象正确销毁 virtual void doSomething() = 0; // 纯虚函数,强制派生类实现 }; class Boo : public Foo { public: void doSomething() override { // 这里写原来doSomething(std::vector<std::unique_ptr<Boo>>&)中针对单个Boo的逻辑 } }; class Doo : public Foo { public: void doSomething() override { // 这里写原来doSomething(std::vector<std::unique_ptr<Doo>>&)中针对单个Doo的逻辑 } };
然后遍历的时候直接调用每个元素的虚函数即可:
for (auto& [key, fooVec] : myMap) { for (auto& fooPtr : fooVec) { fooPtr->doSomething(); } }
这种方式完全不需要任何类型检查或转换,代码更简洁也更符合面向对象的设计原则,如果你能调整原有函数的结构,这绝对是最优选择。
内容的提问来源于stack exchange,提问作者Onur A.

