финальные методы Java против не ++ виртуальных функций C ++ - PullRequest
4 голосов
/ 18 апреля 2011

Являются ли финальные java-методы и невиртуальные методы c ++ разными или одинаковыми? Как?

Ответы [ 5 ]

6 голосов
/ 18 апреля 2011

Они разные.

  • Не виртуальные методы C ++ не отправляются и ничего не переопределяют.

  • Отправляются финальные методы Java, которые могут переопределять методы в своих суперклассах классов.

Однако они похожи в том отношении, что ни виртуальные методы 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.

В эквиваленте C ++, где B :: toString () был не виртуальным, я считаю, что a.toString() не смог отправить в B::toString(). (Я немного заржавел на своем C ++ ...)


(Фактически, JIT-компилятор Java способен обнаруживать случаи, когда виртуальная диспетчеризация не требуется ... без вы объявляете классы или методы как final. Таким образом, истинное назначение final означает, что метод не должен быть переопределен или класс не должен расширяться ... и компилятор Java проверит это за вас.)

4 голосов
/ 18 апреля 2011

Вы все еще можете объявлять не виртуальные функции-члены с той же сигнатурой в наследующих классах в 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: Полиморфизм

Эффективно, однако, оба метода имеют сходные цели: предотвратить переопределение функциив базовом классе.Они просто делают это немного по-другому.

2 голосов
/ 18 апреля 2011

Они очень разные, на самом деле, я бы сказал, совершенно не связанные.

В 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 ++ ничто не мешает вам создать другую функцию в классе Derived с тем же именем (обратите внимание, что другая сигнатура по-прежнему вызовет скрытие всех функций-членов базового класса с тем же именем). Все, что вы получите, - это предупреждение компилятора о том, что функция-член класса Derived скрывает функции-члены базового класса.

Последняя функция-член в Java совершенно другая. В Java все функции-члены являются виртуальными. Таким образом, вы не можете отключить виртуальную диспетчеризацию, как в C ++. Последняя функция-член просто означает, что любому последующему производному классу не будет разрешено (возникнет ошибка) объявлять функцию-член с тем же именем (и подписью). Но виртуальная диспетчеризация (и, следовательно, переопределение в динамическом полиморфном смысле) все еще происходит между интерфейсом / базовым классом, который объявил исходную функцию-член, и производным классом, который пометил ее как final. Просто последующее переопределение функции строго запрещено (т. Е. Попытка вышеуказанного кода с помощью foo (), помеченного как final в базовом классе, приведет к ошибке в объявлении класса Derived, поскольку foo () там не будет допускается).

Как видите, эти два понятия совершенно разные.

  • В C ++, с не виртуальной функцией-членом, виртуальная диспетчеризация не происходит (таким образом, нет «переопределения» в традиционном смысле), но вам разрешено иметь производные классы с функциями-членами с тем же именем (и это может полезно иногда в "статическом полиморфизме").
  • В Java с функцией финального члена виртуальная диспетчеризация все еще происходит, но переопределение в последующих производных классах строго запрещено.
0 голосов
/ 18 апреля 2011

Использование виртуальной и не виртуальной функции в C ++ может иметь значение для производительности, и наоборот, в Java не может быть различия в производительности.В Java, помечая метод как final, речь идет исключительно о ясности и удобстве сопровождения кода (это не поведение по умолчанию и используется относительно редко), а в C ++ не виртуальные функции являются поведением по умолчанию и обычно используются частично, потому что ониимеют лучшие характеристики производительности.

В Java сгенерированный код может отличаться в зависимости от того, как он используется, в то время как C ++ должен генерировать корректность во время компиляции.

например, если JVM обнаруживает, что «виртуальный» метод имеет только одну или две общие реализации, он может встроить эти методы или обрабатывать «виртуальный» метод только с одной используемой реализацией, как если бы она была конечной.

0 голосов
/ 18 апреля 2011

Эффективно, да.В C ++ смысл объявления функции виртуальной заключается в том, что вы можете переопределить ее, по умолчанию они не виртуальные, или финальные.

В Java это противоположная ситуация.Если не указано, функции могут быть переопределены подклассами, но вы должны объявить их окончательными, чтобы предотвратить это.

Если вам интересно, как JVM реализует final, гарантирует ли ссылка на функцию, что она действительно ссылается на то же самое?сам по себе код в области памяти суперклассов, как это было бы в C ++ для не-виртуальных, это не может быть гарантировано.JVM могут оптимизировать то, как они считают нужным, но, в первую очередь, финал таков, что компилятор может помешать вам переопределить последний метод суперкласса для начала.

...