【问题标题】:Dynamic method dispatch in java and c++java和c++中的动态方法调度
【发布时间】:2012-05-29 14:06:19
【问题描述】:

在这个 C++ 程序中,

#include<iostream>
using namespace std;

class Base
{

    public:

    Base()
    {
        cout<<"\n\nBase ctr";
        fun();
    }  

    virtual void fun()
    {
        cout<<"\n\nBase's fun()";
    }
};

class Derived : public Base
{
    public:

    Derived()
    {
        cout<<"\n\nDerived ctr";
        fun();
    }

    virtual void fun()
    {
        cout<<"\n\nDerived's fun()";
    }
};
    int main()
    {
        Derived obj;
        return 0;
    }

当使用 Derived 类对象从 Base 类的构造函数调用虚函数时,会调用 Base 的类 fun(),

输出::
基础点击率

Base 的 fun()

派生的ctr

Derived 的 fun()

原因解释here 但是

在这个java程序中,

class Base
{
    Base()
    {
        System.out.println( "\n\nBase ctr" );
        fun();
    }
    void fun()
    {
        System.out.println( "\n\nBase's fun()" );
    }
}

class Derived extends Base
{
    int a=1;

    Derived()
    {
        System.out.println( "\n\nDerived ctr a = "+a );

        a=8;
        fun();
    }

    void fun()
    {
       System.out.println( "\n\nDerived's fun() a = "+a );
    }

    public static void main(String args[])
    {
       Derived obj = new Derived();
    }
}

输出是 ::
基础点击率

Derived 的 fun() a = 0

导出 ctr a = 1

Derived 的 fun() a = 8

这里调用了派生类fun(),所以constraint应用于c++程序,不适用于JAVA程序。为什么?

【问题讨论】:

  • 因为...它们是不同的语言,并且适用的规则不一样?
  • 这条规则使得推理程序行为变得更加复杂,因为它是特殊情况下的方法调用规则。 Java 是关于简单规则的。
  • @MarkoTopolnik 仔细想想,C++ 方式更有意义。我记得读过 Java 程序员应该避免在构造函数中调用虚方法正是出于这个原因。
  • @Pubby 是的,这更有意义,是的,由于缺少此规则,Java 开发人员倾向于编写错误的习语。然而,由于设计选择保持简单,Java 中没有这条规则和许多其他类似规则仍然是事实。
  • @DavidRodríguez-dribeas 在 java 中,虚拟方法调用的行为始终类似于虚拟方法调用,在 c++ 中,此行为会在对象构造和剩余生命周期之间发生变化(调用的方法取决于 ctor 所处的状态) .我将一致的行为称为一个特性——我从来没有把关于虚拟方法调用的推理问题称为 java 特性,它是任何具有虚拟方法调用的语言的“特性”。 java 优势:Derived 类的开发人员可能希望更改 Base 的部分初始化方式(与任何其他方法一样,有其他方法可以做到这一点)

标签: java c++ dynamic


【解决方案1】:

这两种语言采用不同的方法进行动态调度。在 C++ 中,它只会分派给完全构造的对象。这是通过在构造过程中更改对象的类型来处理的,因为层次构造函数的不同级别开始正在执行。另一方面,Java 甚至在启动最基础的构造函数之前就认为对象是最派生的类型。

Java 方法的问题在于,它实际上可能在尚未构造的对象上执行代码。考虑派生类型有一个初始化为 X 的字段,并且它在方法中被访问/更新。在基础对象上调用该方法将在构造函数初始化之前访问该成员。

无论如何,您都应该避免在任何一种语言的对象的构造函数或析构函数中调用虚函数。


示例:

public class Base {
   public final int x;
   public Base() { 
      x = foo();
   }
   int foo() { return 1; }
}
public class Derived extends Base {
   public final int y;
   public Derived() {
      y = 2;
   }
   int foo() { return y; }
}
Derived d = new Derived();
assert( d.x == d.y );                      // Can this ever fail?

在这段代码中,我们有 2 个最终的int,代码看起来很简单,可以推理。 assert 会失败吗?

【讨论】:

  • ... 所有这一切都引出了一个问题:为什么它的创建者为 Java 选择了这种行为?
  • 这是一种设计选择,它允许其他构造,例如基于覆盖器在构造函数中初始化 final 引用。我不认为这是原因,因为依赖注入将更适合此用例(即派生类型会将值传递给基本构造函数,即使涉及更多编码也使其更清晰)。无论如何,您必须询问设计该语言的人:)
  • 我想我们在所有此类问题中都有一个选择,然后:保持哲学沉默或尝试猜测设计师。人们通常最终沉迷于后者。
  • 是的,您正在展示我们一开始讨论的经典示例,即泄露this 的问题。但是,Java 应该添加一条规则以使这个特定示例更易于推理,同时使许多其他示例变得一团糟的论点并不是特别合理。
  • @DavidRodríguez-dribeas 不知道你的对象会发生什么是虚方法的一个特性 - c++ 只有一个特殊的初始化情况,但它对其他所有事情的行为都是相同的未知方式。我确实相信 java 方式和 c++ 方式都有自己的优点和缺点(它就像大/小端 - 没有最终正确的方法可以让每个人都开心)并且没有 java/c++ 的实际创建者几乎不可能找出他们为什么选择这样做。
猜你喜欢
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-23
  • 1970-01-01
  • 1970-01-01
  • 2015-11-18
  • 1970-01-01
相关资源
最近更新 更多