C++,使用基类指针存储子类和使用派生类方法的策略

C++,Strategy to use base class pointer storing subclasses and use the derived classes' method

本文关键字:派生 类方法 策略 存储 基类 指针 C++ 子类      更新时间:2023-10-16

基类是程序中每个类的基,它提供x_pos和y_pos。

Animal(为清晰起见删除)和Person是Base的直接子类
不同的人和动物可以创建为动物和人的子类
在我的例子中,我创建了王班

我有以下课程

#include <iostream>
class Base{ 
protected:    
    int x_pos;
    int y_pos;
public:
    Base(int x,int y): x_pos(x), y_pos(y){};
    virtual ~Base(){};
    int getX(){return x_pos;};
    int getY(){return y_pos;};
};
class Person : public Base{
protected:
    char sex;
public:
    Person(int x,int y,char sex):Base(x,y), sex(sex){};
    virtual ~Person(){};
    char getSex(){return sex;};
    virtual void haveFun() = 0;
};
class Wang : public Person{
public:
    Wang(int x,int y,char a): Person(x,y,a){};
    ~Wang(){};
    void haveFun(){ std::cout << "Wang is coding" << std::endl;};
};

问题是,出于某种原因,我必须有一个Base指针来存储人或动物。(例如,地板上有一个"底座")

class Floor{
    Base *bs;
public:
    void haveFun(){ 
      // bs->Wang::havefun();
      // I want to achieve this goal
    }
}

如何在不向下投射的情况下访问人/动物和王级别的方法
有什么策略我可以在这里应用吗
(我有点认为vistor的模式可能会应用于此,但我不知道如何应用)

在这里使用双重调度/访问者有点尴尬,因为为Floor提供的代码已经存储了Base*,并且只有一个继承层次结构。在没有进行侵入性设计更改的情况下,类型信息在这一点上已经有点丢失了。

无论使用什么,通常都需要基于实际子类型的某种形式的分支。它可能是第二个虚拟调度,可能是查看类型信息的if/else语句,可能是对dynamic_casts的尝试,并捕获bad_cast(用于引用)或对照nullptr检查结果以查找指针。

在您的情况下,如果您想避免dynamic_cast/RTTI并保持代码基本不变,我实际上会建议这样做:

class Animal;
class Person;
class Base{
public:
    ...
    virtual Person* person() {return nullptr;}
    virtual Animal* animal() {return nullptr;}
    ...
}
class Person: public Base{
public:
    ...
    Person* person() override {return this;}
    ...
}
class Animal: public Base{
public:
    ...
    Animal* animal() override {return this;}
    ...
}

这将CCD_ 8耦合到CCD_。CCD_ 10仍然可以以其他方式(包括编译时报头依赖性)与CCD_ 11解耦。

这确实意味着您必须为要单独处理的每个子类型向Base添加代码,但无论您使用什么(包括访问者),通常都需要在某个地方添加代码。

class Floor{
    Base *bs;
public:
    void haveFun(){ 
      // If `bs` is a person, invoke `haveFun`.
      Person* person = bs->person();
      if (person)
          person->haveFun();
    }
}

这种方法的缺点是,要通过指向Base*的指针在子类型级别上处理的每个新子类型都需要向Base添加新函数(尽管向Base添加新函数不太容易受到脆弱基类综合症的影响)。好处是,你可以,比如说,大幅更改PersonAnimal的接口,而不必更新Base。针对新的需求进行扩展是相当容易的,并且避免了降级(可以说,降级可能更容易被滥用)。这使得设计比在类型之间转换更明确地说明什么是合法行为,并避免了潜在的RTTI要求(如果出于某种原因需要考虑的话)。

如果你想避免下转换(这是一件合理的事情,尽管这不是绝对必要的,因为dynamic_cast<>将提供安全的下转换),你有两个选项:

1) 将您可能希望在基类中调用的所有方法声明为虚拟方法。定义那些什么都不做或返回错误代码(或类似代码)的方法的基类实现,这样,当您在不适当类型的对象上调用这些方法时,就不会发生有害的事情。

2) 在Floor类中保留多个指针;每种类型的对象都有一个,你可能想把它放在那里。这样,当你想调用haveFun()时,你就知道你的Person对象可以通过(Person*)指针而不是(Animal*)指针访问。如果希望每个楼层约束仅强制一个对象,请确保在设置"人物"指针时清除"动物"指针,反之亦然。