在C ++中向下转换继承

时间:2014-10-12 19:36:34

标签: c++ inheritance casting

我有一个名为Animal的课程

class Animal
{
    std::string name;
 public:

    Animal(string n)
    {
        name = n;
    }

    string getname()
    {
        return name;
    }
};

和两个继承的类Cat和Dog

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

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

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

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
        }
    }

};

我想要它,当我进入猫然后它应该进入猫列表和狗也在Enqueue功能相同。我不确定它是如何工作的,我如何输入从动物到猫或狗的演员表。 我试过了

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的签名或者你想要的任何东西,这样可以帮助我清楚地了解继承中的类型转换。

2 个答案:

答案 0 :(得分:4)

供参考,以下是此代码在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
  • string-sink构造函数从其参数移开。
  • 为班级数据成员采用系统的命名约定。
  • 不需要加载局部变量;你可以将任意表达式放入函数调用中。
  • 由于懒惰的原因,我的具体类继承了构造函数。在实际环境中,您可能需要编写自己的构造函数。

多形性:

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

高级设计:

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

答案 1 :(得分:1)

在您的代码中有两个相当明显的问题:

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

动态绑定

动态绑定是指在运行时将名称绑定到函数而不是编译时。如果您正在处理运行时polymorphism,则使用动态绑定允许您使用对象的动态类型调用函数(Dog)而不是静态类型Animal)。这很有用,因为当派生类覆盖继承的基类函数时,最好调用派生类函数而不是基类1。

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

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

virtual string getname();

如果派生类重写此函数,那将是被调用的函数,否则将调用基类的版本。可以将此虚函数视为默认实现。

切片问题

复制派生类实例的基础部分时会发生切片。例如,当您按值获取基类参数并传入派生类时,可能会发生这种情况 - 派生的部分将被&#34;切片&#34;关闭。当发生这种情况时,您只能使用对象的静态部分来调用函数和访问数据 - 因此,为什么在getname()中调用Enqueue()始终会调用Animal::getname()

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

EnqueueAnimal实例进行左值引用:

void Enqueue(Animal const& a);

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


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


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