父类指针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
相关产品推荐
相关产品推荐

