使用基类指针的std::vector如何正确访问派生类成员函数?
解决方案
核心思路:利用C++多态特性省略类型判断
你要的无类型判断的优雅实现本质就是用面向对象的多态特性,通过基类虚函数统一接口,派生类各自实现自身逻辑,运行时自动绑定对应实现,完全不需要手动判断格子类型。
必改基础问题
- 你当前所有派生类都是私有继承cField(class默认继承权限是private),基类指针无法正常访问派生类对象,必须改为
public cField - 基类必须添加虚析构函数,否则通过基类指针释放派生类对象时会出现内存泄漏
- 移除手动维护的type枚举判断逻辑,用虚函数替代
具体实现示例
首先修改基类定义:
#include <cstdint> #include <vector> #include <iostream> #include <memory> #include <string> class cPlayer { public: std::string name; }; class cField { public: uint8_t pos; std::string name{}; // 基类虚析构 virtual ~cField() = default; // 统一接口:玩家访问格子时的触发逻辑,派生类各自实现 virtual void onPlayerVisit(cPlayer* visitor) = 0; // 统一获取格子名称的接口,不需要每个派生类单独写getXXXname virtual std::string getFieldName() { return name; } cField(const uint8_t& _pos, const std::string& _name) : pos(_pos), name(_name) { } };
然后修改派生类,实现自身逻辑:
// 街道格子 class cStreet : public cField { public: uint8_t amountHouses = 0; uint16_t propertyPrice; cPlayer* owner = nullptr; cStreet(const uint8_t& _pos, const std::string& _name, uint16_t _propertyPrice) : cField(_pos, _name), propertyPrice(_propertyPrice) { } // 实现街道的访问逻辑:没主人就提示买,有主人就收租,自己的就提示盖房 void onPlayerVisit(cPlayer* visitor) override { if (!owner) { std::cout << visitor->name << " 到达" << name << ",可花费" << propertyPrice << "购买\n"; // 后续加买地逻辑 } else if (owner != visitor) { uint16_t rent = propertyPrice * (amountHouses + 1) / 10; std::cout << visitor->name << " 到达" << owner->name << "的" << name << ",需支付租金" << rent << "\n"; // 后续加收租逻辑 } else { std::cout << "你到达自己的地产" << name << ",可花费XXX盖房\n"; // 后续加盖房逻辑 } } }; // 火车站格子 class cRailstation : public cField { public: cPlayer* owner = nullptr; uint16_t Price; cRailstation(const uint8_t& _pos, const std::string& _name, uint16_t _Price) : cField(_pos, _name), Price(_Price) { } // 实现火车站的访问逻辑:根据持有火车站数量算租金 void onPlayerVisit(cPlayer* visitor) override { if (!owner) { std::cout << visitor->name << " 到达" << name << ",可花费" << Price << "购买\n"; } else if (owner != visitor) { // 假设每个火车站租金翻倍 uint16_t rent = 100 * /* owner持有火车站数量 */ 1; std::cout << visitor->name << " 到达" << owner->name << "的" << name << ",需支付租金" << rent << "\n"; } } }; // 监狱格子 class cPrison : public cField { public: std::vector<cPlayer*> visitors; std::vector<cPlayer*> prisoners; cPrison(const uint8_t& _pos, const std::string& _name) : cField(_pos, _name) {} void onPlayerVisit(cPlayer* visitor) override { // 是路过还是被关进来,处理对应逻辑 prisoners.push_back(visitor); std::cout << visitor->name << " 被关进监狱,需停留3回合\n"; } }; // 普通特殊格子(比如GO、免费停车) class cSpecialField : public cField { public: enum SpecialType { GO, FREE_PARKING, GO_TO_JAIL }; SpecialType sType; cSpecialField(const uint8_t& _pos, const std::string& _name, SpecialType t) : cField(_pos, _name), sType(t) {} void onPlayerVisit(cPlayer* visitor) override { switch(sType) { case GO: std::cout << visitor->name << " 经过起点,获得2000元\n"; break; case FREE_PARKING: std::cout << visitor->name << " 到达免费停车,无操作\n"; break; case GO_TO_JAIL: std::cout << visitor->name << " 被判入狱,直接传送到监狱格子\n"; // 后续加传送逻辑 break; } } };
然后主函数的遍历逻辑就可以简化到没有任何类型判断:
int main() { uint8_t pos = 0; cPlayer player{"bob"}; cPlayer enemy1{"stuart"}; cPlayer enemy2{"kevin"}; // 用智能指针自动管理内存,不需要手动delete std::vector<std::unique_ptr<cField>> fields; // 构建棋盘 fields.push_back(std::make_unique<cSpecialField>(pos++, "GO", cSpecialField::GO)); fields.push_back(std::make_unique<cStreet>(pos++, "Street 1", 800)); fields.push_back(std::make_unique<cStreet>(pos++, "Street 2", 800)); fields.push_back(std::make_unique<cRailstation>(pos++, "Railstation 1", 300)); fields.push_back(std::make_unique<cStreet>(pos++, "Street 3", 800)); fields.push_back(std::make_unique<cRailstation>(pos++, "Railstation 2", 400)); fields.push_back(std::make_unique<cStreet>(pos++, "Street 4", 800)); fields.push_back(std::make_unique<cStreet>(pos++, "Street 5", 800)); fields.push_back(std::make_unique<cPrison>(pos++, "Prison")); fields.push_back(std::make_unique<cRailstation>(pos++, "Railstation 3", 500)); // 模拟bob依次走到每个格子,完全不需要判断类型 for(auto& field : fields) { field->onPlayerVisit(&player); // 统一获取格子名称,不需要单独调用不同派生类的get方法 std::cout << "当前格子名称:" << field->getFieldName() << "\n\n"; } return 0; }
特殊场景补充
如果确实有需要单独获取某类格子的场景(比如全局统计所有街道的总资产),可以用dynamic_cast安全转换:
uint64_t totalStreetValue = 0; for (auto& field : fields) { if (auto* street = dynamic_cast<cStreet*>(field.get())) { totalStreetValue += street->propertyPrice; } }
dynamic_cast会在运行时检查类型是否匹配,转换失败会返回空指针,比你手动判断type再C风格强转安全很多。
内容的提问来源于stack exchange,提问作者ThomasAlvaEdison
相关产品推荐
相关产品推荐

