【问题标题】:Weird behaviour C++ pure virtuals奇怪的行为 C++ 纯虚拟
【发布时间】:2014-02-03 22:19:58
【问题描述】:

C++ 纯虚类的奇怪行为

请帮助该死的 C++ 初学者更好地理解纯虚拟类。

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

期望/预期输出

1 -> 推文
2 -> 推特

但是,这里的输出是

实际输出

1 -> 喵喵
2 -> 推特

这是为什么呢? 似乎动物类的 operator= 没有效果。 那是因为调用了动物类的标准 operator= 而它什么都不做? 如何在不使用指针的情况下实现类似于 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;
}

【问题讨论】:

  • 是的! Object slicing
  • C++ 引用与 Java 引用有很大不同。如果你想要类似于 Java 引用的东西,你必须使用 C++ pointers.

标签: c++ inheritance reference virtual


【解决方案1】:

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

您的a = b 分配只是将数据从Bird 对象bAnimal 子对象复制到a 引用的Cat 对象的Animal 子对象。由于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`

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

【讨论】:

  • 谢谢。很有帮助。
【解决方案2】:

在 C++ 中引用不能被反弹;它们总是引用您创建它们的变量。在这种情况下,a 是对c 的引用,因此它一直是Cat

当您重新分配引用时,您并没有重新绑定引用 - 您正在分配引用所引用的基础变量。因此分配a = bc = b 相同。

【讨论】:

    【解决方案3】:

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

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

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

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

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

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

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

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

    这里要吸取的教训是永远不要通过具有任何多态性的值对象传递或赋值,否则你最终会对其进行切片。

    【讨论】:

      【解决方案4】:

      试试这个。

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

      【讨论】:

        【解决方案5】:

        删除a = b,你会得到“喵”和“推特”...

        以更笼统的态度:

        首先,为您的泛型类定义一个泛型接口(在此特定示例中为Animal)。

        然后您可以将此接口与任何子类实例一起使用(在此特定示例中为CatBird)。

        每个实例都会根据你的具体实现“行动”。

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-11-12
          • 2018-06-04
          • 1970-01-01
          • 2015-12-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-27
          相关资源
          最近更新 更多