【问题标题】:java final methods vs c++ nonvirtual functionsjava final 方法与 c++ 非虚拟函数
【发布时间】:2011-04-18 04:46:44
【问题描述】:

java final 方法和 c++ 非虚拟方法是不同还是相同?怎么样?

【问题讨论】:

    标签: java c++ final non-virtual-interface


    【解决方案1】:

    它们是不同的。

    • C++ 非虚拟方法不会被调度,也不会覆盖任何东西。

    • Java final 方法被调度,并且可以覆盖其类超类中的方法。

    但是,它们在 C++ 非虚拟方法或 Java 最终方法都不能被覆盖方面是相似的。它们在某种意义上也是相似的,如果您有某个对象的静态类型是所讨论的类型,则运行时系统不需要分派方法调用。

    为了说明区别,请考虑以下两个 Java 类:

    public class A {
        public String toString() {
            return "A";
        }
    }
    
    public class B extends A {
        public final String toString() {
            return "B";
        }
    }
    
    A a = ...
    B b = ...
    a.toString();  // could call A.toString() or B.toString() - dispatch
    b.toString();  // could only call B.toString() - no dispatch required
                   // but it will behave the same as if dispatching had occurred.
    

    在 B::toString() 是非虚拟的 C++ 等效项中,我相信 a.toString() 无法分派到 B::toString()。 (我的 C++ 有点生疏了……)


    (事实上,Java JIT 编译器能够检测不需要虚拟分派的情况...... 无需您将类或方法声明为final。因此,@ 的真正目的987654325@ 是指定一个方法不应被覆盖或一个类不应被扩展......并让Java编译器为您检查。)

    【讨论】:

      【解决方案2】:

      您仍然可以在 C++ 中继承类时声明具有相同签名的非虚拟成员函数,因为 Java 明确禁止声明具有相同签名的方法,其中基类声明该方法为 final。 C++ 中的虚拟性只是帮助在处理继承/多态性时找到要调用的正确函数。

      例子:

      #include <iostream>
      
      class Base
      {
      public:
          void doIt()
          {
              std::cout << "from Base.doIt()" << std::endl;
          }
      };
      
      class Child : public Base
      {
      public:
      
          void doIt()
          {
              std::cout << "from Child.doIt()" << std::endl;
          }
      };
      
      int main()
      {
          Base a;
          a.doIt(); // calls Base.doIt()
          Child b;
          b.doIt(); // calls Child.doIt()
          Base *c = new Base();
          c->doIt(); // calls Base.doIt()
          Child *d = new Child();
          d->doIt(); // calls Child.doIt()
          Base *e = new Child();
          e->doIt(); // calls Base.doIt()
          std::cin.ignore();
          return 0;
      }
      

      Java 中使用 final 的类似示例将导致编译器错误:

      public class Base
      {
          public final void doIt()
          {
              System.out.println("In Base.doIt()");
          }
      }
      
      public class Child extends Base
      {
          public void doIt() // compiler error: Cannot overload the final method from Base
          {
              System.out.println("In Child.doIt()");
          }
      }
      

      有关 C++ 中的多态性的更多说明,请参阅cplusplus.com: Polymorphism

      不过,实际上,这两种方法都有相似的目标:防止覆盖基类中的函数。他们只是以稍微不同的方式去做。

      【讨论】:

      • 我不太确定我是否同意“您仍然可以在 C++ 中的继承类中声明具有相同签名的成员函数,而 Java 明确禁止它。”。当然,您可以在子类中声明具有相同签名的成员函数。否则您将如何覆盖超类的方法?
      • 如果基类声明了 final 方法。我将更新我的答案以使这种区别更清楚-er
      • 我认为需要澄清的是,您可以在子类中拥有与超类中的非虚拟函数具有相同签名的函数,C++ 会让您侥幸逃脱,但如果您引用super,则调用super的方法,但如果引用子类,则调用subclass的方法。
      【解决方案3】:

      它们非常不同,事实上,我会说,完全不相关。

      在 C++ 中,如果基类具有非虚拟成员函数,则可以在派生类中声明具有相同名称的非虚拟成员函数。效果将是派生类的成员函数将隐藏基类的成员函数。并且不会发生虚拟调度。如下:

      struct Base {
        void foo() {
          std::cout << "Base::foo called!" << std::endl;
        };
      };
      
      struct Derived : Base {
        void foo() { 
          std::cout << "Derived::foo called!" << std::endl;
        };
      };
      
      int main() {
        Derived d;
        d.foo();    //OUTPUT: "Derived::foo called!"
        Base* b = &d;
        b->foo();   //OUTPUT: "Base::foo called!"
      };
      

      上面展示了派生类的成员函数是如何隐藏基类函数的。如果您有一个指向基类的指针,由于函数是非虚拟的,因此虚拟表不用于解析调用,因此将调用基类中的 foo 函数。这里的要点是,在 C++ 中,没有什么能阻止您在派生类中创建另一个同名函数(请注意,不同的签名仍然会导致隐藏所有基类的同名成员函数)。您将得到的只是一个编译器警告,告诉您派生类的成员函数隐藏了基类的成员函数。

      Java 中的 final 成员函数完全不同。在 Java 中,所有成员函数都是虚函数。所以你不能像在 C++ 中那样关闭虚拟调度。 final 成员函数只是意味着不允许任何后续派生类(将发生错误)声明具有相同名称(和签名)的成员函数。但是在声明原始成员函数的接口/基类和将其标记为 final 的派生类之间仍然发生虚拟调度(因此,在动态多态意义上,重写)。只是后来严格禁止函数的重写(即在基类中尝试使用 foo() 标记为 final 的上述代码会在 Derived 类的声明中出错,因为 foo() 不会有允许)。

      如您所见,这两个概念完全不同。

      • 在 C++ 中,使用非虚拟成员函数时,不会发生虚拟调度(因此,没有传统意义上的“覆盖”),但您可以拥有具有同名成员函数的派生类(并且它可以有时在“静态多态”中很有用)。
      • 在 Java 中,使用 final 成员函数,仍然会发生虚拟调度,但严格禁止在后续派生类中覆盖。

      【讨论】:

        【解决方案4】:

        使用虚拟与非虚拟函数是 C++ 可以产生性能差异,相反在 Java 中可能没有性能差异。在 Java 中,将方法标记为 final 纯粹是为了代码的清晰性和可维护性(它不是默认行为并且相对很少使用),而在 C++ 中,非虚拟函数是默认行为并且常用部分是因为它们具有更好的性能特征。

        在 Java 中,生成的代码可能因使用方式而异,而 C++ 必须在编译时生成正确性。

        例如如果 JVM 检测到“虚拟”方法只有一个或两个常用实现,它可以内联这些方法或将只使用一个实现的“虚拟”方法视为最终实现。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-21
          • 2015-06-13
          • 2012-02-12
          • 1970-01-01
          • 2015-09-29
          • 2013-05-01
          • 1970-01-01
          • 2010-11-07
          相关资源
          最近更新 更多