在一个数据结构中存储多态类型

Storing polymorphic types in one data structure

本文关键字:存储 多态 类型 数据结构 一个      更新时间:2023-10-16

你有一个动物收容所。庇护所可以存储可变数量的动物。你把许多动物(狗和猫)放进了收容所。

然后你告诉员工随机选择并带给你一些动物。你不知道他选择了什么类型的动物。你告诉他们说话。其中一些会"吠叫",有些会"喵"。

重要!狗可以取,猫不能。如果您确定您选择了一只狗,它应该能够立即获取(例如,无需从动物转换为狗)

这种逻辑怎么实现?(最好没有提升::任何)

下面是一个部分工作的示例:http://ideone.com/kR4788

#include <iostream>
#include <map>
using namespace std;
class Animal {};
class Shelter {
    private:
        std::map<int, Animal*> animals;
    public:
        void Add(Animal* animal) {
            animals[animals.size()] = animal;
        };
        Animal* Select(int index) {
            return animals[index];
        }
};
class Dog: public Animal {
    public:
        void Speak() { cout << "bark" << endl; }
        void Fetch() {}
};
class Cat: public Animal {
    public:
        void Speak() { cout << "meow" << endl; }
};
Shelter shelter;
int main() {
    shelter.Add(new Cat());
    shelter.Add(new Dog());
    // I'd like to make it work like this
    //
    // shelter.Select(0)->Speak(); /* meow */
    // shelter.Select(1)->Speak(); /* bark */
    //
    // Like below but without upcasting to given animal
    ((Cat*) shelter.Select(0))->Speak();
    ((Dog*) shelter.Select(1))->Speak();
    // I know under index 1 is a Dog so it can fetch!
    //
    // shelter.Select(1)->Fetch(); /* no segfault */
    //
    // Like below but without upcasting to given animal
    ((Dog*) shelter.Select(1))->Fetch();

    return 0;
}

编辑:

可以尝试使用 dynamic_castAnimal对象强制转换为Dog,然后调用 fetch 方法:

Dog *foo = dynamic_cast<Dog*>(shelter.Select(1));
if (foo) {
  foo->Fetch();
}

如果dynamic_cast失败,它将返回null因此请确保在使用对象之前检查对象是否未null。有关dynamic_cast的更多信息,请查看此处。


您可以将虚拟函数添加到Animal界面:

class Animal {
  public:
    virtual void speak();
};

此外,在不相关的说明中,您的speak方法似乎没有修改对象,因此您应该考虑将它们放在const

class Animal {
  public:
    virtual void speak() const;
};

您可以在此处找到有关恒定正确性的更多信息。

正如

Aliou注意到的speak应该在Animal中声明为virtual,否则层次结构是相当无用的,或者换句话说,没有多态性。

测试Animal是否是具有dynamic_cast<Dog*>Dog(同时是向上转换)是一个需要考虑的选项。不漂亮,但它有效。

Dog *dog = dynamic_cast<Dog*> shelter.Select(1);
if (dog) dog->Fetch();

dynamic_cast指针永远不会像其他人建议的那样抛出......

另一种解决方案是在Animal中定义virtual Fetch,也许是NOP({}),这样你就不必在不获取的动物中定义它。

根据要求,我将我的评论作为答案。完整的源代码在 Ideone 中:

在动物类中:

class Animal {
  public:
    virtual void Speak() const = 0;
    virtual void Fetch() const { cout << "This animal can't fetch" << endl;};
    virtual ~Animal(){ }
};

需要虚拟析构函数来确保为派生自基类的对象调用正确的析构函数。如果没有虚拟析构函数,将仅调用基类析构函数,而不调用派生对象的析构函数。

在狗中:

void Fetch() const { cout << "fetch" << endl; }

并且主要:

shelter.Select(0)->Speak();
shelter.Select(1)->Speak();
shelter.Select(0)->Fetch();
shelter.Select(1)->Fetch();

您没有要求员工只归还可以取物的动物,因此您必须检查(投射)每只动物可以取物。

另一种方法是为动物添加一个虚拟获取功能,除了狗之外,它什么都不做。