奇怪的行为C ++纯虚拟

时间:2014-02-03 22:19:59

标签: c++ inheritance reference virtual

C ++纯虚拟类的奇怪行为

请帮助一个血腥的C ++ - 初学者更好地理解纯虚拟类。

我尝试了一个使用C ++虚拟的简单示例,但我不确定结果。 如果我在另一种编程语言中尝试使用相同的java,例如输出将是

期望/预期输出

  

1 - >鸣叫
  2 - >鸣叫

但是,这里的输出是

实际输出

  

1 - >喵
  2 - >鸣叫

为什么? 似乎运动员=类动物没有效果。 那是因为叫做类动物的标准算子=什么都没做? 如何在不使用指针的情况下实现与java类似的行为? 这甚至可能吗?

下面是尽可能简化的代码示例:

代码

#include <string>
#include <iostream>

using namespace std;

class Animal
{
public:
    virtual string test() const = 0;
};

class Cat : public Animal
{
public:
    virtual string test() const
    {
        return "Meow";
    }
};

class Bird : public Animal
{
public:
    virtual string test() const
    {
        return "Tweet";
    }
};

void test_method(Animal &a)
{
    Bird b;
    a = b;
    cout << "1 -> " << a.test() << endl;
    cout << "2 -> " << b.test() << endl;
}

int main(int args, char** argv)
{
    Cat c;
    Animal &a = c;
    test_method(a);
    return 0;
}

5 个答案:

答案 0 :(得分:4)

显然,你对a = b任务的行为有一些不切实际的期望。

您的a = b作业只是将Animal Bird对象b子对象的数据复制到Animal Cat对象的a子对象。 1}}。由于Animal根本没有数据,因此a = b为空操作,无操作。它完全没有做任何事情。

但即使它确实做了某些事情,它也只是复制数据字段。它无法复制对象的多态身份。无法在C ++中更改对象的多态身份。没有办法改变对象的类型。无论您做什么,Cat始终都是Cat

您编写的代码可以缩短为

Cat a;
Bird b;

(Animal &) a = b; // Same as `(Animal &) a = (Animal &) b`

a.test(); // still a `Cat`
b.test(); // still a `Bird`

你以更加模糊的方式做了同样的事情。

答案 1 :(得分:1)

在C ++中引用不能反弹;它们总是引用您创建它们的变量。在这种情况下,ac的引用,因此它始终是Cat

当您重新分配引用时,您不会重新引用引用 - 您将分配引用引用的基础变量。因此,作业a = bc = b相同。

答案 2 :(得分:1)

您是所谓的对象切片的受害者。 C ++在处理对象方面与Java和C#不同。在C#中,例如,在创建新实例时,每个用户定义类型都作为引用进行管理,并且只将基本类型和结构作为值处理。这意味着在分配对象时,在Java或C#中,您只分配引用,例如:

Object a = new Object(); 对象b = a;

将导致a和b指向同一个对象(我们分配时创建的对象)。

在C ++中,故事有所不同。您可以在堆或堆栈中创建对象的实例。并且您可以通过引用,指针或值传递所述对象。

如果你设计引用或指针,它的行为类似于C#和Java。但是,如果按值分配对象,即分配实际对象而不是指针或引用,则将创建对象的新副本。默认情况下,C ++中的每个用户定义类型都是可复制的。

当涉及继承和多态时,此复制行为会产生问题,因为当您将子类型复制到父类型时,将创建的副本将仅包含子类型中父类型的信息部分,从而失去了你可能拥有的任何多态性。

在您将Cat对象复制到Animal对象的示例中,只复制了cat的Animal部分,这就是为什么丢失多态,虚拟表不再存在。如果基类是以任何方式抽象的,那么这甚至都不可能。

如果要保留多态性,解决方案是通过指针或引用而不是值传递对象。您可以在堆中创建对象并分配该指针,您可以获取堆栈中对象的地址并确定该指针,或者您可以只获取对象的引用并分配该对象。

这里要学到的教训是永远不要通过任何类型的多态来传递或分配值对象,否则你最终会对它进行切片。

答案 3 :(得分:0)

删除a = b,你会得到“Meow”,然后是“推文”...

以更一般的态度:

首先,为此泛型类定义通用接口(在此特定示例中为Animal)。

然后,您可以将此接口用于此特定示例中的任何子类实例(CatBird)。

每个实例都将根据您的具体实施情况“行动”。

你在函数test_method中的错误是使用了子类的实例而没有通过泛型类(带引用或指针)来引用它。

为了将其更改为Animal个实例的通用函数,您可以执行以下操作:

void test_method(Animal &a)
{
    cout << a.test() << endl;
}

答案 4 :(得分:0)

试试这个。

#include <string>
#include <iostream>

using namespace std;

class Animal
{
public:
    virtual string test() const = 0;
};

class Cat : public Animal
{
public:
    virtual string test() const
    {
        return "Meow";
    }
};

class Bird : public Animal
{
public:
    virtual string test() const
    {
        return "Tweet";
    }
};

void test_method(Animal *a)
{
    Bird *b = new Bird();
    a = b;
    cout << "1 -> " << a->test() << endl;
    cout << "2 -> " << b->test() << endl;
    free(a);
}

int main(int args, char** argv)
{
    Cat *c = new Cat();
    Animal *a = c;
    test_method(a);
    free(c);
    return 0;
}