Оба языка схожи в отношении требований по переопределению с естественными различиями в семантике.В основном оба требуют одинаковых ограничений на вызывающий код (то есть аргументы) и предлагают одинаковые или более строгие гарантии при обработке.Это может звучать немного нечетко, но если учесть это, все просто.
Когда это переопределение
Для функции-члена (метода), чтобы переопределить член базового класса, оба языка требуют, чтобы функция была полиморфной (virtual
в C ++, не final
в Java) имеют одинаковые имена и одинаковые номера типа аргументов.Некоторые языки допускают противоположные типы аргументов, но ни Java, ни C ++ не делают.
Ковариантный тип возвращаемого значения
Ковариант здесь означает, что тип возвращаемого типа изменяется таким же способом , что и типы, в которых реализована функция-член.То есть тип, возвращаемый производной функцией, должен быть полиморфным и быть таким же или производным от того же типа, объявленного в базовом классе.Java является ссылочным языком, и, следовательно, все возвращаемые типы могут демонстрировать полиморфизм, кроме примитивных типов.C ++ является языком value , и только ссылки и указатели являются полиморфными.Это означает, что в Java возвращаемый тип должен точно соответствовать или быть ссылочным типом и быть производным от типа, возвращаемого базой.В C ++ это должен быть указатель или указатель на тот же или производный тип.Как и во введении, причина в том, что если вы вызовете функцию-член через базу, у вас будет объект, который соответствует тому, что вы ожидаете.
Спецификации исключений
Спецификации исключений не очень распространены в C ++, но в Java.Хотя в обоих языках подход к переопределению одинаков: метод переопределения в производном классе должен иметь более жесткие ограничения на то, что может быть выброшено.Различия в языковой поверхности здесь, поскольку Java только проверяет проверено исключений, поэтому это позволит непроверенные исключения в производных типах, которые не были выброшены базой.С другой стороны, производная функция не может добавлять новые проверенные исключения, отсутствующие в базовом классе, опять же, ковариация вступает в игру, и производная функция может генерировать ковариантные исключения.В C ++ спецификации исключений имеют совершенно другое значение, но точно так же спецификация в производном типе должна быть более ограниченной, чем в базовом, и она также допускает ковариантные спецификации исключений.
Основание такое жеЕсли вы пишете блок try {} catch() {}
вокруг вызова через ссылку на базовый тип, и он перехватывает все исключения, объявленные в базе, при вызове переопределения все исключения будут перехвачены в одних и тех же блоках --все возможно непроверенные исключения в Java.
Модификаторы доступа
В Java спецификация доступа к производному методу должна быть как минимум такой же строгой, както из base, то есть, если объявление базовой функции задает protected
, то производная функция не может быть public
, но, с другой стороны, может быть private
, интересно, что Java не позволяет переопределить private
функция в базовом классе.
В C ++ спецификаторы доступа не вступают в игрудля переопределения, и вы можете изменять спецификаторы доступа по своему усмотрению, делая его более или менее ограничительным в производных классах.Кстати, вы можете переопределить член private
в базовом классе (который объявлен virtual
) и который обычно используется для реализации шаблона NVI (не виртуального интерфейса), который должен быть реализован с помощью методов protected
вJava.
Прекратить переопределение
Java позволяет разорвать цепочку переопределения на любом уровне, пометив функцию-член как final
или, альтернативно, сделав ее private
. В C ++ (текущий стандарт) вы не можете разорвать переопределяющую цепочку ни в одной точке, даже в тех случаях, когда final overrider не имеет доступа к функции-члену, которую переопределяет, что создает странный эффект:
struct base {
virtual void f() {}
};
struct derived : private base {
void g() {
f();
}
};
struct most_derived : derived {
void f() { // overrides base::f!!!
//base::f(); // even if it does not have accesss to it
}
};
В этом примере, поскольку наследование является частным на уровне derived
, most_derived
не имеет доступа к подобъекту base
, с его точки зрения, оно не происходит от base
(причина, по которой base::f()
не удастся скомпилировать внутри most_derived::f()
), но, с другой стороны, реализуя функцию с подписью void ()
, она обеспечивает переопределение для base::f
. Вызов g()
для most_derived
объекта будет отправлен на most_derived::f()
, а для derived
объект будет отправлен на base::f()
.