Upcasting/Downcasting 以 class 为基础处理多个派生的 classes

Upcasting/Downcasting to handle mutliple derived classes by base class

假设我有一个基础 class 和多个派生 class es,我想避免每个派生 class 类型都有多个 variables/functions。

我发现实现这一点的唯一方法是使用 up-/downcasting。但特别是沮丧通常被认为是不好的做法 - 那么该怎么做呢?

示例:

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

class Animal{
protected:
    Animal(std::string val) { name = val; }
public:
    virtual ~Animal() = default;
    std::string name;
};

class Cat: public Animal {
public:
    Cat(std::string name) : Animal(name) { }
    ~Cat() = default;

    void purr();
};

class Dog: public Animal {
public:
    Dog(std::string name): Animal(name) { }
    ~Dog() = default;

    void bark();
};

void pet(Animal *animal) {
    if (!animal)
        return;

    if (dynamic_cast<Cat*>(animal))
        std::cout << "petting my cat " << animal->name << std::endl;
    else if (dynamic_cast<Dog*>(animal))
        std::cout << "petting my dog " << animal->name << std::endl;
};

int main()
{
    Animal *currentPet = new Cat("Lucy");
    pet(currentPet);
    
    // after many joyful years - RIP
    delete currentPet;
    currentPet = new Dog("Diego");
    
    pet(currentPet);

    delete currentPet;
    currentPet = NULL;

    return 0;
}

这完全没问题还是仍然被认为是不好的design/practice?

我觉得实现这一目标的最佳方法是在基础 class 中使用虚方法 petted() 并在每个子class 中覆盖它以满足该子[=的需要17=].

class Animal { 
// ...
virtual void petted() { std::cout << "Petted general animal"; }
}
class Dog: public Animal { 
// ...
void petted() { std::cout << "Petted dog [...]"; }
}
class Cat: public Animal {
// ...
void petted() { std::cout << "Petted cat [...]"; } 
}
// this doesn't even have to be a separate function...
void pet(Animal* a) {
 a->petted();
}

将其设为虚拟将使您可以通过 base-class 指针实现运行时多态性(在运行时检查指针指向的实例的实际类型),这将选择正确的“版本” petted() 方法。

至少那是我会做的(到目前为止我只用 C++ 编写了一年半)。

更新

添加 OP 要求的示例。另外,我们可以使用 const 引用来代替 pet 函数中的原始指针。这将使您无需检查空指针。

是的,向下转型应该很少用到。这是一种无需向下转换即可实现所需内容的可能实现。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>

class Animal{
protected:
    std::string name_;
    std::string type_;
    Animal(const std::string& name, const std::string& type) : name_(name),type_(type) 
    {}
public:
    virtual ~Animal() = default;
    virtual std::string sound() const = 0;
    std::string name() const { return name_; }
    std::string type() const { return type_; }
};

class Cat: public Animal {
public:
   Cat(std::string name) : Animal(name,"cat") { }
   ~Cat() = default;
   std::string sound() const override { return "Meow"; }
};

class Dog: public Animal {
public:
    Dog(std::string name): Animal(name, "dog") { }
    ~Dog() = default;
    std::string sound() const override { return "Wolf"; }
};

void pet(const Animal& animal) {
    std::cout << "petting my" << animal.type() << " " << animal.name() << " and I " << animal.sound() << std::endl;
};

int main()
{
    std::shared_ptr<Animal> currentPet = std::make_shared<Cat>("Lucy");
    pet(*currentPet);

    currentPet = std::make_shared<Dog>("Diego");

    pet(*currentPet);

    return 0;
}

我尽可能地保留了你的原始代码,我刚刚删除了向下转换并使用了智能指针。

现在,函数 pet 不需要知道关于 Animal 的派生 classes 的任何具体信息,如果您添加另一个派生 class pet 函数将保持不变,它将处理新 class 自动。