当我使用父类指针将它们添加到向量时,我不能使用子类特定的功能

Posted

技术标签:

【中文标题】当我使用父类指针将它们添加到向量时,我不能使用子类特定的功能【英文标题】:I can't use subclass specific funcitons when I add them to a vector using parent class pointers 【发布时间】:2020-09-05 20:28:55 【问题描述】:

我正在尝试自己创建一个“外星入侵者”游戏。为了创建敌人和玩家,我创建了一个名为“实体”的类并创建了它的子类。比如 Player、shootingEnemy、IdleEnemy。编码时,我意识到将它们收集在 vector<Entity> 中会使我的碰撞检测功能更容易。

在互联网上搜索后,我了解到这称为“对象切片”,并复制对象的任何基本部分。

所以最终版本变成了这个。

int main()

    int BoardWidth = 50;
    int BoardLength = 30;
    vector<Bullet> bullets;
    vector<Entity*> SpaceShips;
    
    setup(SpaceShips, BoardWidth, BoardLength); 

    double ElapsedTime = 0;
    int PreviousRoundSec = 0;
    int PreviousRoundQSec = 0;

    DrawGame(BoardWidth, BoardLength, SpaceShips, bullets);
    int IsGameOver = 0;
    auto start = chrono::steady_clock::now();
    while(!IsGameOver)
    
        // Updates EverySecond
        if ((int)(ElapsedTime / 1000) > PreviousRoundSec)
        
            PreviousRoundSec = (int)(ElapsedTime / 1000);
            

        

        // Updates every quarter of a second
        if ((int)(ElapsedTime / 250) > PreviousRoundQSec)
        
            PreviousRoundQSec = (int)(ElapsedTime / 250);
            
        

        // To keep time
        auto end = chrono::steady_clock::now();
        ElapsedTime = chrono::duration_cast<chrono::milliseconds>(end - start).count();
    
    if (IsGameOver == 1)
    
        // conjualations
    
    else if (IsGameOver == 2)
    
        // GameOver
    

    return 0;

但是当我尝试使用某些特定于子类的函数时,我收到一个编译器错误,提示“CLASS "Entity" 没有任何名为 "shoot" 的成员"。

我正在尝试练习类和多态性,所以我什至不知道这是否有解决方案,因为编译器无法知道该向量的哪个元素属于哪个子类。

这也是我的课程标题页,以备不时之需。

class Entity

public:

    int x;
    int y;

    int width;
    int length;

    int hp;
    bool shooting;

public:

    Entity(int x, int y, int width, int length, int hp, bool shooting): x(x), y(y), width(width), length(length), hp(hp), shooting(shooting) 
;

class Bullet : public Entity

private:
    char dir;
    int ID;

public:
    Bullet(int x, int y, char GivenDir, int GivenID) : Entity(x, y, 1, 1, 1, false)  dir = GivenDir; ID = GivenID; 

    void Move();
    void IfHit(vector<Entity>& SpaceShips);
    void IfOut();

;

class Player : public Entity

private:
    char action = 'a';

public:
    Player(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) 

    void GetAction();
    void Move();
    void Shoot(vector<Bullet>& bullets);
    bool IfHit(vector<Entity>& SpaceShips, vector<Bullet>& bullets);

    
;

class IdleEnemy : public Entity

public:
    
    IdleEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, false)
    
    bool IfHit(Player* player, vector<Bullet> &bullets);
    void Move(char HordDir);
    
;

class ShootingEnemy : public Entity



public:
    ShootingEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) 

    void Shoot(vector<Bullet> &bullets);
    bool IfHit(Player* player, vector<Bullet> &bullets);
    void Move(char HordDir);
;

【问题讨论】:

您可以将指针重新解释为子类指针以访问子类特定的函数。 我该怎么做呢?能给我举个小例子吗? auto ptr = reinterpret_cast(Spaceships[n]); 那你应该可以做ptr->Shoot(); 正如下面的答案所指出的,dynamic_cast 在这里比 reinterpret_cast 更好。 【参考方案1】:

您需要检查 C++ 中的运行时多态性。让我们看看你怎么能做到这一点。首先,您需要更改您的Entity 类接口。您需要添加虚拟或纯虚拟功能。我添加了纯虚函数;

class Entity

public:

    int x;
    int y;

    int width;
    int length;

    int hp;
    bool shooting;

public:

    Entity(int x, int y, int width, int length, int hp, bool shooting) : x(x), y(y), width(width), length(length), hp(hp), shooting(shooting) 

    void virtual Move() = 0; // pure virtual function
    void virtual IfHit() = 0; // pure virtual function 
;

虚函数是可覆盖的函数。此外,它们有实现,但是当我们谈论纯虚函数时,它们只为类提供接口。您需要在派生类中覆盖该函数。当您实现派生类时,您需要这样做,

class Bullet : public Entity

private:
    char dir;
    int ID;

public:
    Bullet(int x, int y, char GivenDir, int GivenID) : Entity(x, y, 1, 1, 1, false)  dir = GivenDir; ID = GivenID; 

    void Move()override;
    void IfHit();
    void IfOut();

;

class Player : public Entity

private:
    char action = 'a';

public:
    Player(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) 

    void GetAction();
    void Move();
    void Shoot(vector<Bullet>& bullets);
    void IfHit()override //code;


;

class IdleEnemy : public Entity

public:

    IdleEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, false) 

    void IfHit()override;
    void Move()override;

;

class ShootingEnemy : public Entity



public:
    ShootingEnemy(int x, int y, int hp) : Entity(x, y, 3, 2, hp, true) 

    void Shoot(vector<Bullet>& bullets);
    void IfHit()override;
    void Move()override;
;

这些函数既可以内联实现,也可以在源文件中实现。此外,这些函数还有一个重点是返回值、函数签名和名称必须相同,除非您不使用协变返回类型。

从派生类中可以看出,一些函数并不常见。我知道你的问题我该如何使用它:) 正如在 cmets 中提到的 ttemple 你需要使用 dynamic_cast 运算符。

int main()


    Entity* ptr = new ShootingEnemy 1,2,4 ;
    ptr->Move();
    ptr->IfHit();

    if (auto SE = dynamic_cast<ShootingEnemy*>(ptr))
        SE->Shoot(...);

dynamic_cast 运算符是运行时转换运算符。它将基类指针的类型转换为派生类。它被称为向下转换。此外,它检查基类指针是否指向目标派生类。如果dynamic_cast 操作以失败完成,则返回null 并且if statement 变为失败。通过这种方式,您可以使用运行时多态性和类成员函数。

顺便说一句,尽量避免对象切片。您正在丢失派生类属性。

为了更好的理解,请参考classesdynamic_cast

【讨论】:

非常感谢。特别是用于显示不止一个解决方案。这很有帮助。【参考方案2】:

编译器告诉你真相。你有一个指向Entity 的指针,它的接口中显然没有Shoot 方法,那么你怎么可能在没有任何强制转换的情况下调用它呢?

您在此处尝试实现的动态多态性背后的想法是拥有一个通用接口(您的基类,Entity),每个接口都有特定的实现子类。因此,公开可用的方法签名将适用于所有子类,但不是实现。

从设计的角度来看,最简洁的方法是将Entity 重命名为ShootableEntity 并在其中声明一个纯虚拟Shoot 方法。那么所有的子类都应该提供一些实现。

如果不是所有这些都实现Shoot,但您正试图以这种方式通用地使用它们,也许您应该重新考虑这种方法,例如。创建两个容器 - 用于可射击实体和不可射击实体。然后,在迭代可射击实体(实际上是 ShootableEntity 子类的类的实例,其中包含 Shoot 声明)时,您可以毫无问题地在基类的指针上调用 Shoot

但是,您的Entity 不代表任何通用接口。所以,如果你试图利用多态性(所以,你有一个指向基类的指针,但在那个指针后面有一些具体的实例),这样的类对你没有任何好处。

其实文档本身就有很好的解释:http://www.cplusplus.com/doc/tutorial/polymorphism/

【讨论】:

非常感谢。

以上是关于当我使用父类指针将它们添加到向量时,我不能使用子类特定的功能的主要内容,如果未能解决你的问题,请参考以下文章

添加指向数组的指针时出现分段错误

使用static_cast进行父类指针转子类指针可能出现的问题

向量的变化第一指针

删除指针向量时遇到问题

七LSP 里氏替换原则

C++父类子类,使用向量