You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用基类指针的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 02:15:05