C++中的向下投射继承

Down Casting Inheritance in C++

本文关键字:继承 C++      更新时间:2023-10-16

我有一个类叫Animal

class Animal
{
    std::string name;
 public:
    Animal(string n)
    {
        name = n;
    }
    string getname()
    {
        return name;
    }
};

和两个继承类猫和狗

class Cat : public Animal
{
public:
    Cat(string name):Animal(name)
    {}
};
class Dog : public Animal
{
public:
    Dog(string name):Animal(name){}
};

我有一个名为AnimalQueue的类,它包含两个列表,猫列表和狗列表

class AnimalQueue
{
    std::list<Cat*> cats;
    std::list<Dog*> dogs;
public:
    void Enqueue(Animal a)
    {
        if(a.getname().compare("Cat") == 0)
        {
            // what should i do here
        }
        else
        {
            // what should i do here
        }
    }
};

我想要什么,当我输入cat时,它应该会进入cat列表,并且在排队功能中与dog相同。我不确定它会如何工作,我如何才能键入从动物到猫或狗的角色。我试过

Cat *c = (Cat*) &a; 

但它不起作用。

int main()
{
    string name = "Cat";
    Cat c(name);
    name = "Dog";
    Dog d(name);
    AnimalQueue aq;
    aq.Enqueue(c);
    aq.Enqueue(d);
}

这是工作代码,您可以在编辑器中复制粘贴。你可以更改Animal的签名或任何你想要的东西,这样可以帮助我明确继承中的类型转换概念。

为了参考,下面是这些代码在C++中的样子:

#include <string>
#include <utility>
#include <vector>
class Animal
{
    std::string name_;
public:
    explicit Animal(std::string name) : name_(std::move(name)) {}
    virtual ~Animal() {}
};
class Cat : public Animal { using Animal::Animal; };
class Dog : public Animal { using Animal::Animal; };
class AnimalQueue
{
    std::vector<Cat> cats_;
    std::vector<Dog> dogs_;
public:
    void Enqueue(Animal const & animal)
    {
        if (Cat const * p = dynamic_cast<Cat const *>(&animal))
        {
            cats_.push_back(*p);
        }
        else if (Dog const * p = dynamic_cast<Dog const *>(&animal))
        {
            dogs_.push_back(*p);
        }
        else
        {
            // discarding unrecognized animal
        }
    }
};

用法:

AnimalQueue q;
q.Enqueue(Dog("dog"));
q.Enqueue(Cat("cat"));

注意:

C++基础:

  • 不要说using namespace std
  • 选择的容器是std::vector,而不是std::list
  • 避免隐式转换。除非另有要求,否则所有内容均为explicit
  • 字符串接收器构造函数从其参数中移出
  • 对类数据成员采用系统的命名约定
  • 不需要加载局部变量;您可以将任意表达式放入函数调用中
  • 我的具体类由于懒惰而继承构造函数。在实际环境中,您可能需要编写自己的构造函数

多态性:

  • 多态基有一个虚拟析构函数
  • 当参数作为指针或引用传递到基子对象时,可以使用多态性

高级设计:

  • 正如所写的那样,似乎没有什么理由拥有多态库,因为您的整个代码都是静态的。几个不同的过载(对于CatDog)将更合适
  • 当具体类型直到运行时才能知道时,需要多态基。在这种情况下,您将没有具体类型的变量,并且需要动态创建对象。当这种情况发生时,您将希望绕过std::unique_ptr<Animal>,并且必须调整动态强制转换

代码中有两个问题非常明显:

  • 您不允许动态绑定
  • 您有一个切片问题

动态绑定

动态绑定是指在运行时而不是编译时将名称绑定到函数。如果您正在处理运行时多态性,则使用动态绑定可以使用对象(Dog)的动态类型而不是其静态类型

要在C++中实现动态绑定,需要合并虚拟函数。如果使用虚拟函数,编译器将延迟评估函数的名称,直到运行时才可以使用对象的虚拟表1将名称绑定到正确函数的地址。

要赋予getname()动态绑定,请使用virtual说明符声明它:

virtual string getname();

如果派生类重写了此函数,则将调用该函数,否则将调用基类的版本。这个虚拟函数可以被认为是一个默认的实现。

切片问题

复制派生类实例的基部分时会发生切片。例如,当你按值获取基类参数并传入派生类时,就会发生这种情况——派生部分将是";切片的";当这种情况发生时,您只有对象的静态部分可以调用函数和访问数据,因此在Enqueue()中调用getname()总是调用Animal::getname()

要防止这种情况,必须使用基类的引用或指针。这可以防止切片,因为在传递引用或指针时不会发生复制。

使Enqueue获取对Animal实例的左值引用:

void Enqueue(Animal const& a);

重要:如果使用基类指针指向派生实例,为了防止"未定义行为",必须为基类提供虚拟析构函数。这样做允许将派生类的析构函数与基类的析构因子一起调用。


这只是对您的问题的解释,您可以参考@Kerrek SB的答案来获得一个好的解决方案。


1虚拟表是一种非标准的实现,它实际上取决于您的系统如何解析虚拟函数名。