【问题标题】:Are virtual methods for methods that are not created until runtime?是直到运行时才创建的方法的虚拟方法吗?
【发布时间】:2024-03-08 21:05:02
【问题描述】:

我在理解 C++ 中虚拟方法的用途时遇到了一些麻烦。如果方法的对象不是在编译时创建的,方法是否必须是虚拟的?例如,如果您必须在运行时选择一只农场动物,那么该动物的所有方法都需要是虚拟的,因为在用户选择之前您不知道它是否会被创建。如果我错了,请纠正我。

【问题讨论】:

  • 为什么这个标签是“C”? C++ 没有“方法”;它们被称为“成员函数”。成员函数没有“它的对象”。一本好书可能是学习 C++ 的良好开端。
  • 您不能在运行时创建方法(即在 C++ 中),也不能在运行时创建对象(除了一些包含模板实例或类似的“对象”的奇异定义)。
  • @delnan:如果new MyClass(); 在运行时不创建对象,它会做什么?模板不在运行时实例化,它们基本上是编译时代码生成器。
  • @Kleist:是的,当然。我这边的一个错字,我的意思是写“编译时” - 我指的是:“如果它的对象不是在编译时创建的,方法是否必须是虚拟的?”

标签: c++ methods runtime virtual


【解决方案1】:

不,这完全不正确。如果需要根据对象的 type 选择方法并且 type 在编译时未知,则该方法必须是虚拟的。如果您的代码如下所示:

Animal *x;
if(y==2)
{
    x = new Animal();
    x->DoSomething();
}

编译器在运行时知道x 的类型是“动物”。所以它知道要调用哪个版本的“DoSomething”。但是看看这段代码:

Animal *x;
if(y==1) x=new Zebra();
else if (y==2) x=GetSomeAnimal();
else x=new Giraffe();
x->DoSomething();

这里,x 的类型在编译时是未知的。它可以是斑马,也可以是长颈鹿,也可以是GetSomeAnimal 函数返回的任何类型的动物。没有办法知道DoSomething 的调用是否应该调用Zebra::DoSomethingGiraffe::DoSomething 或其他东西。所以Animal::DoSomething 需要是虚拟的。

为了表明它与将要创建的内容无关,请考虑一下:

void MyFunction(Animal &x)
{
    x.DoSomething();
}

void MyOtherFunction(int x)
{
   Giraffe g;
   Zebra z;
   if(x==2) MyFunction(g);
      else MyFunction(f);
}

这里非常清楚将创建一个Giraffe 和一个Zebra。但是如果Animal::DoSomething 不是虚拟的,MyFunction 将两次调用Animal::DoSomething,而不是 Giraffe 上的 Giraffe::DoSomething 和 Zebra 上的 Zebra::DoSomething。 (当然,如果这是您想要的,请不要将方法设为虚拟。)

【讨论】:

  • 严格来说,x->DoSomething() 是一个虚拟调用,即使x 始终属于同一类型并且在编译时可以证明这一点。编译器可能会对其进行优化,但在向初学者解释这个概念时这并不重要。
  • 我认为我的信息不清楚,我并不是说是否会创建动物,而是是否会创建子动物之一。而不是 -> 在处理指向对象的指针时使用?
  • @delnan 我不确定你到底想说什么。我们正在讨论该方法是否必须是虚拟的。在第一种情况下,该方法不必是虚拟的。第二种情况,如果不是虚拟的,的事情就会发生。 (假设意图是调用DoSomething 的特定类型版本。)
  • @SaxSalute 在这种情况下,x 是指向 Animal 的指针。引用也会发生同样的事情。问题不在于创建的内容,而在于x 可能指向的内容。在编译时,无法知道 x 在运行时将指向哪种类型的 Animal,并且相同的代码可能一次指向一种动物,而另一次指向另一种动物。
  • @DavidSchwartz 如果第一个示例是针对非virtual DoSomething,那么您的回答更有意义。我根本没有考虑过这一点,因为(1)在这样的 sn-p 中拥有指针不会有任何其他好处,并且(2)您对 sn-ps 的解释是关于运行时类型的。请原谅我的困惑。
【解决方案2】:

了解虚拟方法的用途here。我还建议你看看"Object Oriented Programming in ANSI C" book。

【讨论】:

    【解决方案3】:

    嗯,virtual 是 C++ 内置的东西,用于支持 OOD's 多态性(特别是 run-time or dynamic 多态性)。

    当您希望同一类型的不同对象 (Animal) 表现不同时(getProduceName() 返回“猪肉”或“牛肉”或“鸡蛋”或...),具体取决于上下文(Animal 是否实际上是PigCowChicken 或 ...)您使该行为/功能 virtual

    通常,一个好的 OOD 应该有很好的接口/实现分离。这在 C++ 中是使用继承以及abstract 类/方法来实现的。

    所以实际上,当您制作getProduceName() 多态/virtual 时,您实际上是在尝试从不同的实现(PigCowChicken 或...)中提取接口(@98​​7654334@)保护您的客户端代码免受不同实现的影响。这样,如果你必须支持一个新的实现,你不必更改客户端代码,只要它遵循接口Animal

    那么回答这个问题: 如果一个方法的对象不是在编译时创建的,它是否必须是虚拟的?

    方法是否是虚拟的不取决于对象的创建时间(编译/运行时)。这取决于您希望如何设计您的应用程序。制作函数virtual 可以帮助您从不同的实现中提取接口。如果某个函数 (getOwnerName()) 在所有实现类 ({return ownerName;}) 中具有相同的实现,则不需要将其设为虚拟。

    PS:恕我直言,多态性是良好 OOD 的副产品,您不会设计自己的类来实现“运行时多态性”,然后当您拥有良好的 OO 设计时就会表现出运行时多态性。

    【讨论】:

      【解决方案4】:

      虚函数的更大用途之一是允许旧代码调用新代码

      考虑一下。函数将 Car 对象作为参数。假设它执行 test_drive()、refuel()、calculate_ability()、getStoppingDistance() 等操作。所有这些方法都取决于您传入的汽车类型。

      但是每年都有新车问世。现在,在现实世界中,我们只需导入一个 XML 文件,其中包含构成所有汽车的属性。但是假设为了争论,我们必须静态地这样做:每次我们发现新车时,我们都必须重新构建我们的整个程序。如果我们的程序很大,这将非常不方便。在某种程度上,我们调用了相同的函数——但在某种程度上我们不是——因为对象的类型不同。为什么要重新编译?

      虚拟函数来救援!当汽车制造商发布新车时,他们会同时发布一个声明新类型的头文件(继承自具有虚函数的公共基类)和一个库(定义特定方法的目标文件)。因此,我们无需重新编译我们的应用程序,而是重新链接到这个新库。因此,旧代码(我们的应用程序)能够调用新代码(汽车附带的新库)。

      【讨论】:

        【解决方案5】:

        假设你有这个代码:

        Animal * a = new Pig();
        

        指针a静态类型Animal动态类型Pig

        现在假设我们正在调用

        a->MakeASound();
        

        如果MakeASound 是虚拟的,则调用动态类型(Pig) 的MakeASound 方法。

        如果不是,则调用静态类型(Animal)的MakeASound,无论Pig是否覆盖MakeASound方法。

        【讨论】:

          最近更新 更多