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

父类指针vector中调用子类方法:除virtual外是否有更好方案?

问题描述

我开发的程序包含父类Player,以及Warrior和Vampire两个子类。程序从settings.txt文件读取玩家信息,创建对应Player对象并存储在vector<Player*>类型的arena容器中(例如执行arena.push_back(new Warrior)添加对象),玩家类型(Warrior或Vampire)同样从该文件读取。

我为Warrior和Vampire分别实现了Warrior::attack和Vampire::attack方法,但将Warrior对象存入vector后,无法直接调用Warrior::attack方法。我已通过虚函数实现了多态调用,但想了解是否存在更优的替代方案?在将new Warrior创建的对象指针存入父类指针vector的场景下,能否不使用virtual来调用子类方法?

目前我已尝试的方案:在父类中声明虚方法,子类中定义同名方法,以此实现对应子类对象的attack方法调用。以下是我的实现代码:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using std::cout, std::endl, std::ifstream,std::string,std::vector,std::ostream;


class Player {
public:
    static size_t baseHitPoints;
    static size_t baseStrength;

protected:
    size_t hitPoints;
    size_t strength;
public:
    //父类构造函数
    Player(): hitPoints(baseHitPoints), strength(baseStrength) {}
    //检查玩家是否存活
    bool isAlive() const {
        return hitPoints > 0;
    }
    //虚析构函数以支持子类多态
    virtual ~Player() = default;
    //攻击另一名Player对象p
    void initattacks(Player& other){
        if(this->hitPoints>0 and other.hitPoints >0 and other.hitPoints>this->strength)other.hitPoints= other.hitPoints-this->strength;
        else if(other.hitPoints<=(this->strength))other.hitPoints=0;
    }
    //虚攻击方法,会根据调用对象选择Warrior::attack或Vampire::attack
    virtual void attacks(Player& other){
        this->initattacks(other);
    }
    //依赖子类类型的打印方法
    virtual ostream& print(ostream& output)const{
        output<<"Player DATA";
        return output;
    }
    //为什么virtual和friend不能共存?
    //重载运算符以简化打印操作
    friend ostream& operator <<(ostream& output, const Player& other){
        return other.print(output);
    }

};

//子类1:Warrior
class Warrior : public Player{
public:
//构造函数
    Warrior():Player(){
        this->Player::hitPoints=Player::hitPoints+bonus;
        this->Player::strength=Player::strength+bonus;
    }
    //static成员需单独初始化,例如Warrior::bonus = ...
    //是否需要在创建类之前初始化?
    static size_t bonus;
    void attacks(Player& other)override{
        this->Player::initattacks(other);
    }
    //虚函数重写打印方法,是否有更简便的方式?
    ostream& print(ostream& output)const override{
        cout<<"Warrior ("<<Player::hitPoints<<"HP, "<<strength<<"S)"<<endl;
        return output;
    }
};
//子类2:Vampire
class Vampire : public Player{
public:
    //构造函数
    Vampire():Player(){
    }
    static size_t drain;
    void attacks(Player& other)override{
        this->initattacks(other);
        this->Player::hitPoints= this->Player::hitPoints+ drain;
    }
    ostream& print(ostream& output)const override{
        cout<<"Vampire ("<<Player::hitPoints<<"HP, "<<strength <<"S)"<<endl;
        return output;
    }
};

//初始化static成员变量
//为什么必须这样做?否则代码无法运行?
size_t Player::baseHitPoints;
size_t Player::baseStrength;
size_t Warrior::bonus;
size_t Vampire::drain;

int main(){
    //将玩家存储在vector中,后续可拆分为两个队伍的vector
    vector <Player*> arena;
    ifstream input;
    input.open("settings.txt");
    string temp;
    unsigned int x;
    unsigned int y;
    unsigned int z;
    input>>temp;
    input>>x>>y;
    Player::baseHitPoints=x;
    Player::baseStrength=y;
    while (input.peek() != EOF){
        input >> temp;
        if(temp == "Warrior"){
            input>>z;
            Warrior::bonus=z;
            arena.push_back(new Warrior);
            
        }
        if(temp == "Vampire"){
            input>>z;
            Vampire::drain=z;
            arena.push_back(new Vampire);
        }
        
    }
    //战斗循环
    for(unsigned int i =0; i<100;i++){
        arena[i%2]->attacks(*(arena[(i+1)%2]));
        if(((arena[(i+1)%2])->isAlive())==0){
            cout<<"Winner is :"<<endl;
            cout<<*(arena[i%2]);
            cout<<"Looser is :"<<endl;
            cout<<*(arena[(i+1)%2]);
            break;
        }
    }
        if (arena[0]->isAlive() and arena[1]->isAlive()){
            cout<<"No Winner nor looser :"<<endl;
            cout<<*(arena[0]);
            cout<<*(arena[1]);

        }
    input.close();
}
解决方案与分析

1. 能否不使用virtual调用子类方法?

在你当前的父类指针容器存储子类对象的场景下,不使用虚函数直接调用子类方法的方案不仅不优雅,还存在极高的维护风险,常见的替代方式有以下几种,但都不如虚函数合适:

  • 手动类型判断+强制转换:通过dynamic_cast判断指针实际类型,再转换后调用对应方法。示例代码:
    if (auto warrior = dynamic_cast<Warrior*>(arena[i])) {
        warrior->attacks(other);
    } else if (auto vampire = dynamic_cast<Vampire*>(arena[i])) {
        vampire->attacks(other);
    }
    
    这种方式需要每次新增子类都修改判断逻辑,违背开闭原则,代码冗余且容易出错。
  • 使用variant存储对象:C++17及以上可以用std::variant<Warrior, Vampire>代替父类指针容器,通过std::visit调用对应方法。但这种方式失去了父类的抽象能力,且如果子类数量较多,代码复杂度会急剧上升。

综上,虚函数是当前场景下实现多态的最优解,它是C++为这种面向对象场景设计的原生机制,代码简洁、符合开闭原则,维护成本最低。

2. 现有代码的优化建议

除了多态实现,你的代码还有几个可以优化的点:

  • static成员变量问题:当前Player::baseHitPoints、Warrior::bonus等static成员是类级别的变量,所有同类型玩家共享同一个值。如果settings.txt中要创建多个Warrior,后面的Warrior会覆盖前面的bonus值,这不符合需求。建议把这些属性改成实例成员,在创建对象时传入对应值。
  • 构造函数写法优化:Warrior构造函数中直接修改Player::hitPoints的写法不规范,应该在初始化列表中处理,或者给父类添加带参数的构造函数,子类调用时传递修改后的值。示例:
    class Player {
    public:
        Player(size_t hp, size_t str) : hitPoints(hp), strength(str) {}
        // ...
    };
    
    class Warrior : public Player {
    public:
        Warrior(size_t baseHp, size_t baseStr, size_t bonus) 
            : Player(baseHp + bonus, baseStr + bonus) {}
        // ...
    };
    
  • print函数修复:子类的print函数直接使用cout输出,而非传入的output参数,这会导致重载的operator<<无法指定输出流(比如输出到文件),应该改成:
    ostream& print(ostream& output) const override {
        output << "Warrior (" << hitPoints << "HP, " << strength << "S)" << endl;
        return output;
    }
    
  • 内存泄漏问题:用new创建的对象没有被delete,程序结束会导致内存泄漏。建议使用智能指针std::unique_ptr<Player>代替裸指针,容器改成vector<std::unique_ptr<Player>>,无需手动管理内存。
  • 关于virtual和friend不能共存:friend函数不是类的成员函数,因此不能被声明为virtual。你当前通过虚函数print配合友元operator<<的写法是正确的,这是实现多态输出的标准方式。

内容的提问来源于stack exchange,提问作者Manmeet Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:23:09